Merge branch 'main' into Hargata/consolidated.report
This commit is contained in:
130
Controllers/APIController.cs
Normal file
130
Controllers/APIController.cs
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
using CarCareTracker.External.Interfaces;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Controllers
|
||||||
|
{
|
||||||
|
[Authorize]
|
||||||
|
public class APIController : Controller
|
||||||
|
{
|
||||||
|
private readonly IVehicleDataAccess _dataAccess;
|
||||||
|
private readonly INoteDataAccess _noteDataAccess;
|
||||||
|
private readonly IServiceRecordDataAccess _serviceRecordDataAccess;
|
||||||
|
private readonly IGasRecordDataAccess _gasRecordDataAccess;
|
||||||
|
private readonly ICollisionRecordDataAccess _collisionRecordDataAccess;
|
||||||
|
private readonly ITaxRecordDataAccess _taxRecordDataAccess;
|
||||||
|
private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
|
||||||
|
private readonly IUpgradeRecordDataAccess _upgradeRecordDataAccess;
|
||||||
|
private readonly IReminderHelper _reminderHelper;
|
||||||
|
private readonly IGasHelper _gasHelper;
|
||||||
|
public APIController(IVehicleDataAccess dataAccess,
|
||||||
|
IGasHelper gasHelper,
|
||||||
|
IReminderHelper reminderHelper,
|
||||||
|
INoteDataAccess noteDataAccess,
|
||||||
|
IServiceRecordDataAccess serviceRecordDataAccess,
|
||||||
|
IGasRecordDataAccess gasRecordDataAccess,
|
||||||
|
ICollisionRecordDataAccess collisionRecordDataAccess,
|
||||||
|
ITaxRecordDataAccess taxRecordDataAccess,
|
||||||
|
IReminderRecordDataAccess reminderRecordDataAccess,
|
||||||
|
IUpgradeRecordDataAccess upgradeRecordDataAccess)
|
||||||
|
{
|
||||||
|
_dataAccess = dataAccess;
|
||||||
|
_noteDataAccess = noteDataAccess;
|
||||||
|
_serviceRecordDataAccess = serviceRecordDataAccess;
|
||||||
|
_gasRecordDataAccess = gasRecordDataAccess;
|
||||||
|
_collisionRecordDataAccess = collisionRecordDataAccess;
|
||||||
|
_taxRecordDataAccess = taxRecordDataAccess;
|
||||||
|
_reminderRecordDataAccess = reminderRecordDataAccess;
|
||||||
|
_upgradeRecordDataAccess = upgradeRecordDataAccess;
|
||||||
|
_gasHelper = gasHelper;
|
||||||
|
_reminderHelper = reminderHelper;
|
||||||
|
}
|
||||||
|
public IActionResult Index()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Route("/api/vehicles")]
|
||||||
|
public IActionResult Vehicles()
|
||||||
|
{
|
||||||
|
var result = _dataAccess.GetVehicles();
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
[Route("/api/vehicle/servicerecords")]
|
||||||
|
public IActionResult ServiceRecords(int vehicleId)
|
||||||
|
{
|
||||||
|
var vehicleRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
|
||||||
|
var result = vehicleRecords.Select(x => new ServiceRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString() });
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
[Route("/api/vehicle/repairrecords")]
|
||||||
|
public IActionResult RepairRecords(int vehicleId)
|
||||||
|
{
|
||||||
|
var vehicleRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
|
||||||
|
var result = vehicleRecords.Select(x => new ServiceRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString() });
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
[Route("/api/vehicle/upgraderecords")]
|
||||||
|
public IActionResult UpgradeRecords(int vehicleId)
|
||||||
|
{
|
||||||
|
var vehicleRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
|
||||||
|
var result = vehicleRecords.Select(x => new ServiceRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString() });
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
[Route("/api/vehicle/taxrecords")]
|
||||||
|
public IActionResult TaxRecords(int vehicleId)
|
||||||
|
{
|
||||||
|
var result = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
[Route("/api/vehicle/gasrecords")]
|
||||||
|
public IActionResult GasRecords(int vehicleId, bool useMPG, bool useUKMPG)
|
||||||
|
{
|
||||||
|
var vehicleRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
|
||||||
|
var result = _gasHelper.GetGasRecordViewModels(vehicleRecords, useMPG, useUKMPG).Select(x => new GasRecordExportModel { Date = x.Date, Odometer = x.Mileage.ToString(), Cost = x.Cost.ToString(), FuelConsumed = x.Gallons.ToString(), FuelEconomy = x.MilesPerGallon.ToString()});
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
[Route("/api/vehicle/reminders")]
|
||||||
|
public IActionResult Reminders(int vehicleId)
|
||||||
|
{
|
||||||
|
var currentMileage = GetMaxMileage(vehicleId);
|
||||||
|
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId);
|
||||||
|
var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage).Select(x=> new ReminderExportModel { Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes});
|
||||||
|
return Json(results);
|
||||||
|
}
|
||||||
|
private int GetMaxMileage(int vehicleId)
|
||||||
|
{
|
||||||
|
var numbersArray = new List<int>();
|
||||||
|
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
|
||||||
|
if (serviceRecords.Any())
|
||||||
|
{
|
||||||
|
numbersArray.Add(serviceRecords.Max(x => x.Mileage));
|
||||||
|
}
|
||||||
|
var repairRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
|
||||||
|
if (repairRecords.Any())
|
||||||
|
{
|
||||||
|
numbersArray.Add(repairRecords.Max(x => x.Mileage));
|
||||||
|
}
|
||||||
|
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
|
||||||
|
if (gasRecords.Any())
|
||||||
|
{
|
||||||
|
numbersArray.Add(gasRecords.Max(x => x.Mileage));
|
||||||
|
}
|
||||||
|
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
|
||||||
|
if (upgradeRecords.Any())
|
||||||
|
{
|
||||||
|
numbersArray.Add(upgradeRecords.Max(x => x.Mileage));
|
||||||
|
}
|
||||||
|
return numbersArray.Any() ? numbersArray.Max() : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,14 +13,17 @@ namespace CarCareTracker.Controllers
|
|||||||
public class LoginController : Controller
|
public class LoginController : Controller
|
||||||
{
|
{
|
||||||
private IDataProtector _dataProtector;
|
private IDataProtector _dataProtector;
|
||||||
|
private ILoginHelper _loginHelper;
|
||||||
private readonly ILogger<LoginController> _logger;
|
private readonly ILogger<LoginController> _logger;
|
||||||
public LoginController(
|
public LoginController(
|
||||||
ILogger<LoginController> logger,
|
ILogger<LoginController> logger,
|
||||||
IDataProtectionProvider securityProvider
|
IDataProtectionProvider securityProvider,
|
||||||
|
ILoginHelper loginHelper
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_dataProtector = securityProvider.CreateProtector("login");
|
_dataProtector = securityProvider.CreateProtector("login");
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_loginHelper = loginHelper;
|
||||||
}
|
}
|
||||||
public IActionResult Index()
|
public IActionResult Index()
|
||||||
{
|
{
|
||||||
@@ -37,30 +40,19 @@ namespace CarCareTracker.Controllers
|
|||||||
//compare it against hashed credentials
|
//compare it against hashed credentials
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var configFileContents = System.IO.File.ReadAllText(StaticHelper.UserConfigPath);
|
var loginIsValid = _loginHelper.ValidateUserCredentials(credentials);
|
||||||
var existingUserConfig = System.Text.Json.JsonSerializer.Deserialize<UserConfig>(configFileContents);
|
if (loginIsValid)
|
||||||
if (existingUserConfig is not null)
|
|
||||||
{
|
{
|
||||||
//create hashes of the login credentials.
|
AuthCookie authCookie = new AuthCookie
|
||||||
var hashedUserName = Sha256_hash(credentials.UserName);
|
|
||||||
var hashedPassword = Sha256_hash(credentials.Password);
|
|
||||||
//compare against stored hash.
|
|
||||||
if (hashedUserName == existingUserConfig.UserNameHash &&
|
|
||||||
hashedPassword == existingUserConfig.UserPasswordHash)
|
|
||||||
{
|
{
|
||||||
//auth success, create auth cookie
|
Id = 1, //this is hardcoded for now
|
||||||
//encrypt stuff.
|
UserName = credentials.UserName,
|
||||||
AuthCookie authCookie = new AuthCookie
|
ExpiresOn = DateTime.Now.AddDays(credentials.IsPersistent ? 30 : 1)
|
||||||
{
|
};
|
||||||
Id = 1, //this is hardcoded for now
|
var serializedCookie = JsonSerializer.Serialize(authCookie);
|
||||||
UserName = credentials.UserName,
|
var encryptedCookie = _dataProtector.Protect(serializedCookie);
|
||||||
ExpiresOn = DateTime.Now.AddDays(credentials.IsPersistent ? 30 : 1)
|
Response.Cookies.Append("ACCESS_TOKEN", encryptedCookie, new CookieOptions { Expires = new DateTimeOffset(authCookie.ExpiresOn) });
|
||||||
};
|
return Json(true);
|
||||||
var serializedCookie = JsonSerializer.Serialize(authCookie);
|
|
||||||
var encryptedCookie = _dataProtector.Protect(serializedCookie);
|
|
||||||
Response.Cookies.Append("ACCESS_TOKEN", encryptedCookie, new CookieOptions { Expires = new DateTimeOffset(authCookie.ExpiresOn) });
|
|
||||||
return Json(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -22,13 +22,18 @@ namespace CarCareTracker.Controllers
|
|||||||
private readonly ICollisionRecordDataAccess _collisionRecordDataAccess;
|
private readonly ICollisionRecordDataAccess _collisionRecordDataAccess;
|
||||||
private readonly ITaxRecordDataAccess _taxRecordDataAccess;
|
private readonly ITaxRecordDataAccess _taxRecordDataAccess;
|
||||||
private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
|
private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
|
||||||
|
private readonly IUpgradeRecordDataAccess _upgradeRecordDataAccess;
|
||||||
private readonly IWebHostEnvironment _webEnv;
|
private readonly IWebHostEnvironment _webEnv;
|
||||||
private readonly bool _useDescending;
|
private readonly bool _useDescending;
|
||||||
private readonly IConfiguration _config;
|
private readonly IConfiguration _config;
|
||||||
private readonly IFileHelper _fileHelper;
|
private readonly IFileHelper _fileHelper;
|
||||||
|
private readonly IGasHelper _gasHelper;
|
||||||
|
private readonly IReminderHelper _reminderHelper;
|
||||||
|
|
||||||
public VehicleController(ILogger<VehicleController> logger,
|
public VehicleController(ILogger<VehicleController> logger,
|
||||||
IFileHelper fileHelper,
|
IFileHelper fileHelper,
|
||||||
|
IGasHelper gasHelper,
|
||||||
|
IReminderHelper reminderHelper,
|
||||||
IVehicleDataAccess dataAccess,
|
IVehicleDataAccess dataAccess,
|
||||||
INoteDataAccess noteDataAccess,
|
INoteDataAccess noteDataAccess,
|
||||||
IServiceRecordDataAccess serviceRecordDataAccess,
|
IServiceRecordDataAccess serviceRecordDataAccess,
|
||||||
@@ -36,6 +41,7 @@ namespace CarCareTracker.Controllers
|
|||||||
ICollisionRecordDataAccess collisionRecordDataAccess,
|
ICollisionRecordDataAccess collisionRecordDataAccess,
|
||||||
ITaxRecordDataAccess taxRecordDataAccess,
|
ITaxRecordDataAccess taxRecordDataAccess,
|
||||||
IReminderRecordDataAccess reminderRecordDataAccess,
|
IReminderRecordDataAccess reminderRecordDataAccess,
|
||||||
|
IUpgradeRecordDataAccess upgradeRecordDataAccess,
|
||||||
IWebHostEnvironment webEnv,
|
IWebHostEnvironment webEnv,
|
||||||
IConfiguration config)
|
IConfiguration config)
|
||||||
{
|
{
|
||||||
@@ -43,11 +49,14 @@ namespace CarCareTracker.Controllers
|
|||||||
_dataAccess = dataAccess;
|
_dataAccess = dataAccess;
|
||||||
_noteDataAccess = noteDataAccess;
|
_noteDataAccess = noteDataAccess;
|
||||||
_fileHelper = fileHelper;
|
_fileHelper = fileHelper;
|
||||||
|
_gasHelper = gasHelper;
|
||||||
|
_reminderHelper = reminderHelper;
|
||||||
_serviceRecordDataAccess = serviceRecordDataAccess;
|
_serviceRecordDataAccess = serviceRecordDataAccess;
|
||||||
_gasRecordDataAccess = gasRecordDataAccess;
|
_gasRecordDataAccess = gasRecordDataAccess;
|
||||||
_collisionRecordDataAccess = collisionRecordDataAccess;
|
_collisionRecordDataAccess = collisionRecordDataAccess;
|
||||||
_taxRecordDataAccess = taxRecordDataAccess;
|
_taxRecordDataAccess = taxRecordDataAccess;
|
||||||
_reminderRecordDataAccess = reminderRecordDataAccess;
|
_reminderRecordDataAccess = reminderRecordDataAccess;
|
||||||
|
_upgradeRecordDataAccess = upgradeRecordDataAccess;
|
||||||
_webEnv = webEnv;
|
_webEnv = webEnv;
|
||||||
_config = config;
|
_config = config;
|
||||||
_useDescending = bool.Parse(config[nameof(UserConfig.UseDescending)]);
|
_useDescending = bool.Parse(config[nameof(UserConfig.UseDescending)]);
|
||||||
@@ -94,39 +103,121 @@ namespace CarCareTracker.Controllers
|
|||||||
_serviceRecordDataAccess.DeleteAllServiceRecordsByVehicleId(vehicleId) &&
|
_serviceRecordDataAccess.DeleteAllServiceRecordsByVehicleId(vehicleId) &&
|
||||||
_collisionRecordDataAccess.DeleteAllCollisionRecordsByVehicleId(vehicleId) &&
|
_collisionRecordDataAccess.DeleteAllCollisionRecordsByVehicleId(vehicleId) &&
|
||||||
_taxRecordDataAccess.DeleteAllTaxRecordsByVehicleId(vehicleId) &&
|
_taxRecordDataAccess.DeleteAllTaxRecordsByVehicleId(vehicleId) &&
|
||||||
_noteDataAccess.DeleteNoteByVehicleId(vehicleId) &&
|
_noteDataAccess.DeleteAllNotesByVehicleId(vehicleId) &&
|
||||||
_reminderRecordDataAccess.DeleteAllReminderRecordsByVehicleId(vehicleId) &&
|
_reminderRecordDataAccess.DeleteAllReminderRecordsByVehicleId(vehicleId) &&
|
||||||
|
_upgradeRecordDataAccess.DeleteAllUpgradeRecordsByVehicleId(vehicleId) &&
|
||||||
_dataAccess.DeleteVehicle(vehicleId);
|
_dataAccess.DeleteVehicle(vehicleId);
|
||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
[HttpPost]
|
|
||||||
public IActionResult SaveNoteToVehicle(Note newNote)
|
|
||||||
{
|
|
||||||
//check if there is already an existing note for this vehicle.
|
|
||||||
var existingNote = _noteDataAccess.GetNoteByVehicleId(newNote.VehicleId);
|
|
||||||
if (existingNote.Id != default)
|
|
||||||
{
|
|
||||||
newNote.Id = existingNote.Id;
|
|
||||||
}
|
|
||||||
var result = _noteDataAccess.SaveNoteToVehicleId(newNote);
|
|
||||||
return Json(result);
|
|
||||||
}
|
|
||||||
[HttpGet]
|
|
||||||
public IActionResult GetNoteByVehicleId(int vehicleId)
|
|
||||||
{
|
|
||||||
var existingNote = _noteDataAccess.GetNoteByVehicleId(vehicleId);
|
|
||||||
if (existingNote.Id != default)
|
|
||||||
{
|
|
||||||
return Json(existingNote.NoteText);
|
|
||||||
}
|
|
||||||
return Json("");
|
|
||||||
}
|
|
||||||
#region "Bulk Imports"
|
#region "Bulk Imports"
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public IActionResult GetBulkImportModalPartialView(ImportMode mode)
|
public IActionResult GetBulkImportModalPartialView(ImportMode mode)
|
||||||
{
|
{
|
||||||
return PartialView("_BulkDataImporter", mode);
|
return PartialView("_BulkDataImporter", mode);
|
||||||
}
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult ExportFromVehicleToCsv(int vehicleId, ImportMode mode)
|
||||||
|
{
|
||||||
|
if (vehicleId == default)
|
||||||
|
{
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
|
string uploadDirectory = "temp/";
|
||||||
|
string uploadPath = Path.Combine(_webEnv.WebRootPath, uploadDirectory);
|
||||||
|
if (!Directory.Exists(uploadPath))
|
||||||
|
Directory.CreateDirectory(uploadPath);
|
||||||
|
if (mode == ImportMode.ServiceRecord)
|
||||||
|
{
|
||||||
|
var fileNameToExport = $"temp/{Guid.NewGuid()}.csv";
|
||||||
|
var fullExportFilePath = _fileHelper.GetFullFilePath(fileNameToExport, false);
|
||||||
|
var vehicleRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
|
||||||
|
if (vehicleRecords.Any())
|
||||||
|
{
|
||||||
|
var exportData = vehicleRecords.Select(x => new ServiceRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString("C"), Notes = x.Notes, Odometer = x.Mileage.ToString() });
|
||||||
|
using (var writer = new StreamWriter(fullExportFilePath))
|
||||||
|
{
|
||||||
|
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||||
|
{
|
||||||
|
csv.WriteRecords(exportData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Json($"/{fileNameToExport}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mode == ImportMode.RepairRecord)
|
||||||
|
{
|
||||||
|
var fileNameToExport = $"temp/{Guid.NewGuid()}.csv";
|
||||||
|
var fullExportFilePath = _fileHelper.GetFullFilePath(fileNameToExport, false);
|
||||||
|
var vehicleRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
|
||||||
|
if (vehicleRecords.Any())
|
||||||
|
{
|
||||||
|
var exportData = vehicleRecords.Select(x => new ServiceRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString("C"), Notes = x.Notes, Odometer = x.Mileage.ToString() });
|
||||||
|
using (var writer = new StreamWriter(fullExportFilePath))
|
||||||
|
{
|
||||||
|
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||||
|
{
|
||||||
|
csv.WriteRecords(exportData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Json($"/{fileNameToExport}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mode == ImportMode.UpgradeRecord)
|
||||||
|
{
|
||||||
|
var fileNameToExport = $"temp/{Guid.NewGuid()}.csv";
|
||||||
|
var fullExportFilePath = _fileHelper.GetFullFilePath(fileNameToExport, false);
|
||||||
|
var vehicleRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
|
||||||
|
if (vehicleRecords.Any())
|
||||||
|
{
|
||||||
|
var exportData = vehicleRecords.Select(x => new ServiceRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString("C"), Notes = x.Notes, Odometer = x.Mileage.ToString() });
|
||||||
|
using (var writer = new StreamWriter(fullExportFilePath))
|
||||||
|
{
|
||||||
|
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||||
|
{
|
||||||
|
csv.WriteRecords(exportData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Json($"/{fileNameToExport}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mode == ImportMode.TaxRecord) {
|
||||||
|
var fileNameToExport = $"temp/{Guid.NewGuid()}.csv";
|
||||||
|
var fullExportFilePath = _fileHelper.GetFullFilePath(fileNameToExport, false);
|
||||||
|
var vehicleRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
|
||||||
|
if (vehicleRecords.Any())
|
||||||
|
{
|
||||||
|
var exportData = vehicleRecords.Select(x => new TaxRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString("C"), Notes = x.Notes });
|
||||||
|
using (var writer = new StreamWriter(fullExportFilePath))
|
||||||
|
{
|
||||||
|
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||||
|
{
|
||||||
|
csv.WriteRecords(exportData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Json($"/{fileNameToExport}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (mode == ImportMode.GasRecord)
|
||||||
|
{
|
||||||
|
var fileNameToExport = $"temp/{Guid.NewGuid()}.csv";
|
||||||
|
var fullExportFilePath = _fileHelper.GetFullFilePath(fileNameToExport, false);
|
||||||
|
var vehicleRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
|
||||||
|
bool useMPG = bool.Parse(_config[nameof(UserConfig.UseMPG)]);
|
||||||
|
bool useUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)]);
|
||||||
|
vehicleRecords = vehicleRecords.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList();
|
||||||
|
var convertedRecords = _gasHelper.GetGasRecordViewModels(vehicleRecords, useMPG, useUKMPG);
|
||||||
|
var exportData = convertedRecords.Select(x => new GasRecordExportModel { Date = x.Date.ToString(), Cost = x.Cost.ToString(), FuelConsumed = x.Gallons.ToString(), FuelEconomy = x.MilesPerGallon.ToString(), Odometer = x.Mileage.ToString() });
|
||||||
|
using (var writer = new StreamWriter(fullExportFilePath))
|
||||||
|
{
|
||||||
|
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
|
||||||
|
{
|
||||||
|
csv.WriteRecords(exportData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Json($"/{fileNameToExport}");
|
||||||
|
}
|
||||||
|
return Json(false);
|
||||||
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult ImportToVehicleIdFromCsv(int vehicleId, ImportMode mode, string fileName)
|
public IActionResult ImportToVehicleIdFromCsv(int vehicleId, ImportMode mode, string fileName)
|
||||||
{
|
{
|
||||||
@@ -216,6 +307,19 @@ namespace CarCareTracker.Controllers
|
|||||||
};
|
};
|
||||||
_collisionRecordDataAccess.SaveCollisionRecordToVehicle(convertedRecord);
|
_collisionRecordDataAccess.SaveCollisionRecordToVehicle(convertedRecord);
|
||||||
}
|
}
|
||||||
|
else if (mode == ImportMode.UpgradeRecord)
|
||||||
|
{
|
||||||
|
var convertedRecord = new UpgradeRecord()
|
||||||
|
{
|
||||||
|
VehicleId = vehicleId,
|
||||||
|
Date = DateTime.Parse(importModel.Date),
|
||||||
|
Mileage = int.Parse(importModel.Odometer, NumberStyles.Any),
|
||||||
|
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Upgrade Record on {importModel.Date}" : importModel.Description,
|
||||||
|
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
||||||
|
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any)
|
||||||
|
};
|
||||||
|
_upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(convertedRecord);
|
||||||
|
}
|
||||||
else if (mode == ImportMode.TaxRecord)
|
else if (mode == ImportMode.TaxRecord)
|
||||||
{
|
{
|
||||||
var convertedRecord = new TaxRecord()
|
var convertedRecord = new TaxRecord()
|
||||||
@@ -251,71 +355,7 @@ namespace CarCareTracker.Controllers
|
|||||||
//check if the user uses MPG or Liters per 100km.
|
//check if the user uses MPG or Liters per 100km.
|
||||||
bool useMPG = bool.Parse(_config[nameof(UserConfig.UseMPG)]);
|
bool useMPG = bool.Parse(_config[nameof(UserConfig.UseMPG)]);
|
||||||
bool useUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)]);
|
bool useUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)]);
|
||||||
var computedResults = new List<GasRecordViewModel>();
|
var computedResults = _gasHelper.GetGasRecordViewModels(result, useMPG, useUKMPG);
|
||||||
int previousMileage = 0;
|
|
||||||
decimal unFactoredConsumption = 0.00M;
|
|
||||||
int unFactoredMileage = 0;
|
|
||||||
//perform computation.
|
|
||||||
for (int i = 0; i < result.Count; i++)
|
|
||||||
{
|
|
||||||
var currentObject = result[i];
|
|
||||||
decimal convertedConsumption;
|
|
||||||
if (useUKMPG && useMPG)
|
|
||||||
{
|
|
||||||
//if we're using UK MPG and the user wants imperial calculation insteace of l/100km
|
|
||||||
//if UK MPG is selected then the gas consumption are stored in liters but need to convert into UK gallons for computation.
|
|
||||||
convertedConsumption = currentObject.Gallons / 4.546M;
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
convertedConsumption = currentObject.Gallons;
|
|
||||||
}
|
|
||||||
if (i > 0)
|
|
||||||
{
|
|
||||||
var deltaMileage = currentObject.Mileage - previousMileage;
|
|
||||||
var gasRecordViewModel = new GasRecordViewModel()
|
|
||||||
{
|
|
||||||
Id = currentObject.Id,
|
|
||||||
VehicleId = currentObject.VehicleId,
|
|
||||||
Date = currentObject.Date.ToShortDateString(),
|
|
||||||
Mileage = currentObject.Mileage,
|
|
||||||
Gallons = convertedConsumption,
|
|
||||||
Cost = currentObject.Cost,
|
|
||||||
DeltaMileage = deltaMileage,
|
|
||||||
CostPerGallon = (currentObject.Cost / convertedConsumption)
|
|
||||||
};
|
|
||||||
if (currentObject.IsFillToFull)
|
|
||||||
{
|
|
||||||
//if user filled to full.
|
|
||||||
gasRecordViewModel.MilesPerGallon = useMPG ? ((unFactoredMileage + deltaMileage) / (unFactoredConsumption + convertedConsumption)) : 100 / ((unFactoredMileage + deltaMileage) / (unFactoredConsumption + convertedConsumption));
|
|
||||||
//reset unFactored vars
|
|
||||||
unFactoredConsumption = 0;
|
|
||||||
unFactoredMileage = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
unFactoredConsumption += convertedConsumption;
|
|
||||||
unFactoredMileage += deltaMileage;
|
|
||||||
gasRecordViewModel.MilesPerGallon = 0;
|
|
||||||
}
|
|
||||||
computedResults.Add(gasRecordViewModel);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
computedResults.Add(new GasRecordViewModel()
|
|
||||||
{
|
|
||||||
Id = currentObject.Id,
|
|
||||||
VehicleId = currentObject.VehicleId,
|
|
||||||
Date = currentObject.Date.ToShortDateString(),
|
|
||||||
Mileage = currentObject.Mileage,
|
|
||||||
Gallons = convertedConsumption,
|
|
||||||
Cost = currentObject.Cost,
|
|
||||||
DeltaMileage = 0,
|
|
||||||
MilesPerGallon = 0,
|
|
||||||
CostPerGallon = (currentObject.Cost / convertedConsumption)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
previousMileage = currentObject.Mileage;
|
|
||||||
}
|
|
||||||
if (_useDescending)
|
if (_useDescending)
|
||||||
{
|
{
|
||||||
computedResults = computedResults.OrderByDescending(x => DateTime.Parse(x.Date)).ThenByDescending(x => x.Mileage).ToList();
|
computedResults = computedResults.OrderByDescending(x => DateTime.Parse(x.Date)).ThenByDescending(x => x.Mileage).ToList();
|
||||||
@@ -353,7 +393,8 @@ namespace CarCareTracker.Controllers
|
|||||||
Date = result.Date.ToShortDateString(),
|
Date = result.Date.ToShortDateString(),
|
||||||
Files = result.Files,
|
Files = result.Files,
|
||||||
Gallons = result.Gallons,
|
Gallons = result.Gallons,
|
||||||
IsFillToFull = result.IsFillToFull
|
IsFillToFull = result.IsFillToFull,
|
||||||
|
MissedFuelUp = result.MissedFuelUp
|
||||||
};
|
};
|
||||||
var vehicleIsElectric = _dataAccess.GetVehicleById(convertedResult.VehicleId).IsElectric;
|
var vehicleIsElectric = _dataAccess.GetVehicleById(convertedResult.VehicleId).IsElectric;
|
||||||
var viewModel = new GasRecordInputContainer()
|
var viewModel = new GasRecordInputContainer()
|
||||||
@@ -541,19 +582,22 @@ namespace CarCareTracker.Controllers
|
|||||||
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
|
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
|
||||||
var collisionRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
|
var collisionRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
|
||||||
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
|
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
|
||||||
|
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
|
||||||
if (year != default)
|
if (year != default)
|
||||||
{
|
{
|
||||||
serviceRecords.RemoveAll(x => x.Date.Year != year);
|
serviceRecords.RemoveAll(x => x.Date.Year != year);
|
||||||
gasRecords.RemoveAll(x => x.Date.Year != year);
|
gasRecords.RemoveAll(x => x.Date.Year != year);
|
||||||
collisionRecords.RemoveAll(x => x.Date.Year != year);
|
collisionRecords.RemoveAll(x => x.Date.Year != year);
|
||||||
taxRecords.RemoveAll(x => x.Date.Year != year);
|
taxRecords.RemoveAll(x => x.Date.Year != year);
|
||||||
|
upgradeRecords.RemoveAll(x => x.Date.Year != year);
|
||||||
}
|
}
|
||||||
var viewModel = new CostMakeUpForVehicle
|
var viewModel = new CostMakeUpForVehicle
|
||||||
{
|
{
|
||||||
ServiceRecordSum = serviceRecords.Sum(x => x.Cost),
|
ServiceRecordSum = serviceRecords.Sum(x => x.Cost),
|
||||||
GasRecordSum = gasRecords.Sum(x => x.Cost),
|
GasRecordSum = gasRecords.Sum(x => x.Cost),
|
||||||
CollisionRecordSum = collisionRecords.Sum(x => x.Cost),
|
CollisionRecordSum = collisionRecords.Sum(x => x.Cost),
|
||||||
TaxRecordSum = taxRecords.Sum(x => x.Cost)
|
TaxRecordSum = taxRecords.Sum(x => x.Cost),
|
||||||
|
UpgradeRecordSum = upgradeRecords.Sum(x=>x.Cost)
|
||||||
};
|
};
|
||||||
return PartialView("_CostMakeUpReport", viewModel);
|
return PartialView("_CostMakeUpReport", viewModel);
|
||||||
}
|
}
|
||||||
@@ -591,94 +635,19 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
numbersArray.Add(gasRecords.Max(x => x.Mileage));
|
numbersArray.Add(gasRecords.Max(x => x.Mileage));
|
||||||
}
|
}
|
||||||
|
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
|
||||||
|
if (upgradeRecords.Any())
|
||||||
|
{
|
||||||
|
numbersArray.Add(upgradeRecords.Max(x => x.Mileage));
|
||||||
|
}
|
||||||
return numbersArray.Any() ? numbersArray.Max() : 0;
|
return numbersArray.Any() ? numbersArray.Max() : 0;
|
||||||
}
|
}
|
||||||
private List<ReminderRecordViewModel> GetRemindersAndUrgency(int vehicleId)
|
private List<ReminderRecordViewModel> GetRemindersAndUrgency(int vehicleId)
|
||||||
{
|
{
|
||||||
var currentMileage = GetMaxMileage(vehicleId);
|
var currentMileage = GetMaxMileage(vehicleId);
|
||||||
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId);
|
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId);
|
||||||
List<ReminderRecordViewModel> reminderViewModels = new List<ReminderRecordViewModel>();
|
List<ReminderRecordViewModel> results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage);
|
||||||
foreach (var reminder in reminders)
|
return results;
|
||||||
{
|
|
||||||
var reminderViewModel = new ReminderRecordViewModel()
|
|
||||||
{
|
|
||||||
Id = reminder.Id,
|
|
||||||
VehicleId = reminder.VehicleId,
|
|
||||||
Date = reminder.Date,
|
|
||||||
Mileage = reminder.Mileage,
|
|
||||||
Description = reminder.Description,
|
|
||||||
Notes = reminder.Notes,
|
|
||||||
Metric = reminder.Metric
|
|
||||||
};
|
|
||||||
if (reminder.Metric == ReminderMetric.Both)
|
|
||||||
{
|
|
||||||
if (reminder.Date < DateTime.Now)
|
|
||||||
{
|
|
||||||
reminderViewModel.Urgency = ReminderUrgency.PastDue;
|
|
||||||
reminderViewModel.Metric = ReminderMetric.Date;
|
|
||||||
}
|
|
||||||
else if (reminder.Mileage < currentMileage)
|
|
||||||
{
|
|
||||||
reminderViewModel.Urgency = ReminderUrgency.PastDue;
|
|
||||||
reminderViewModel.Metric = ReminderMetric.Odometer;
|
|
||||||
}
|
|
||||||
else if (reminder.Date < DateTime.Now.AddDays(7))
|
|
||||||
{
|
|
||||||
//if less than a week from today or less than 50 miles from current mileage then very urgent.
|
|
||||||
reminderViewModel.Urgency = ReminderUrgency.VeryUrgent;
|
|
||||||
//have to specify by which metric this reminder is urgent.
|
|
||||||
reminderViewModel.Metric = ReminderMetric.Date;
|
|
||||||
}
|
|
||||||
else if (reminder.Mileage < currentMileage + 50)
|
|
||||||
{
|
|
||||||
reminderViewModel.Urgency = ReminderUrgency.VeryUrgent;
|
|
||||||
reminderViewModel.Metric = ReminderMetric.Odometer;
|
|
||||||
}
|
|
||||||
else if (reminder.Date < DateTime.Now.AddDays(30))
|
|
||||||
{
|
|
||||||
reminderViewModel.Urgency = ReminderUrgency.Urgent;
|
|
||||||
reminderViewModel.Metric = ReminderMetric.Date;
|
|
||||||
}
|
|
||||||
else if (reminder.Mileage < currentMileage + 100)
|
|
||||||
{
|
|
||||||
reminderViewModel.Urgency = ReminderUrgency.Urgent;
|
|
||||||
reminderViewModel.Metric = ReminderMetric.Odometer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (reminder.Metric == ReminderMetric.Date)
|
|
||||||
{
|
|
||||||
if (reminder.Date < DateTime.Now)
|
|
||||||
{
|
|
||||||
reminderViewModel.Urgency = ReminderUrgency.PastDue;
|
|
||||||
}
|
|
||||||
else if (reminder.Date < DateTime.Now.AddDays(7))
|
|
||||||
{
|
|
||||||
reminderViewModel.Urgency = ReminderUrgency.VeryUrgent;
|
|
||||||
}
|
|
||||||
else if (reminder.Date < DateTime.Now.AddDays(30))
|
|
||||||
{
|
|
||||||
reminderViewModel.Urgency = ReminderUrgency.Urgent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (reminder.Metric == ReminderMetric.Odometer)
|
|
||||||
{
|
|
||||||
if (reminder.Mileage < currentMileage)
|
|
||||||
{
|
|
||||||
reminderViewModel.Urgency = ReminderUrgency.PastDue;
|
|
||||||
reminderViewModel.Metric = ReminderMetric.Odometer;
|
|
||||||
}
|
|
||||||
else if (reminder.Mileage < currentMileage + 50)
|
|
||||||
{
|
|
||||||
reminderViewModel.Urgency = ReminderUrgency.VeryUrgent;
|
|
||||||
}
|
|
||||||
else if (reminder.Mileage < currentMileage + 100)
|
|
||||||
{
|
|
||||||
reminderViewModel.Urgency = ReminderUrgency.Urgent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reminderViewModels.Add(reminderViewModel);
|
|
||||||
}
|
|
||||||
return reminderViewModels;
|
|
||||||
}
|
}
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public IActionResult GetVehicleHaveUrgentOrPastDueReminders(int vehicleId)
|
public IActionResult GetVehicleHaveUrgentOrPastDueReminders(int vehicleId)
|
||||||
@@ -739,5 +708,89 @@ namespace CarCareTracker.Controllers
|
|||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
#region "Upgrade Records"
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetUpgradeRecordsByVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
var result = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
|
||||||
|
if (_useDescending)
|
||||||
|
{
|
||||||
|
result = result.OrderByDescending(x => x.Date).ThenByDescending(x => x.Mileage).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = result.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList();
|
||||||
|
}
|
||||||
|
return PartialView("_UpgradeRecords", result);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult SaveUpgradeRecordToVehicleId(UpgradeRecordInput upgradeRecord)
|
||||||
|
{
|
||||||
|
//move files from temp.
|
||||||
|
upgradeRecord.Files = upgradeRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
|
||||||
|
var result = _upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(upgradeRecord.ToUpgradeRecord());
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetAddUpgradeRecordPartialView()
|
||||||
|
{
|
||||||
|
return PartialView("_UpgradeRecordModal", new UpgradeRecordInput());
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetUpgradeRecordForEditById(int upgradeRecordId)
|
||||||
|
{
|
||||||
|
var result = _upgradeRecordDataAccess.GetUpgradeRecordById(upgradeRecordId);
|
||||||
|
//convert to Input object.
|
||||||
|
var convertedResult = new UpgradeRecordInput
|
||||||
|
{
|
||||||
|
Id = result.Id,
|
||||||
|
Cost = result.Cost,
|
||||||
|
Date = result.Date.ToShortDateString(),
|
||||||
|
Description = result.Description,
|
||||||
|
Mileage = result.Mileage,
|
||||||
|
Notes = result.Notes,
|
||||||
|
VehicleId = result.VehicleId,
|
||||||
|
Files = result.Files
|
||||||
|
};
|
||||||
|
return PartialView("_UpgradeRecordModal", convertedResult);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult DeleteUpgradeRecordById(int upgradeRecordId)
|
||||||
|
{
|
||||||
|
var result = _upgradeRecordDataAccess.DeleteUpgradeRecordById(upgradeRecordId);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
#region "Notes"
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetNotesByVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
var result = _noteDataAccess.GetNotesByVehicleId(vehicleId);
|
||||||
|
return PartialView("_Notes", result);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult SaveNoteToVehicleId(Note note)
|
||||||
|
{
|
||||||
|
var result = _noteDataAccess.SaveNoteToVehicle(note);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetAddNotePartialView()
|
||||||
|
{
|
||||||
|
return PartialView("_NoteModal", new Note());
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetNoteForEditById(int noteId)
|
||||||
|
{
|
||||||
|
var result = _noteDataAccess.GetNoteById(noteId);
|
||||||
|
return PartialView("_NoteModal", result);
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult DeleteNoteById(int noteId)
|
||||||
|
{
|
||||||
|
var result = _noteDataAccess.DeleteNoteById(noteId);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
ServiceRecord = 0,
|
ServiceRecord = 0,
|
||||||
RepairRecord = 1,
|
RepairRecord = 1,
|
||||||
GasRecord = 2,
|
GasRecord = 2,
|
||||||
TaxRecord = 3
|
TaxRecord = 3,
|
||||||
|
UpgradeRecord = 4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
29
External/Implementations/NoteDataAccess.cs
vendored
29
External/Implementations/NoteDataAccess.cs
vendored
@@ -9,16 +9,24 @@ namespace CarCareTracker.External.Implementations
|
|||||||
{
|
{
|
||||||
private static string dbName = StaticHelper.DbName;
|
private static string dbName = StaticHelper.DbName;
|
||||||
private static string tableName = "notes";
|
private static string tableName = "notes";
|
||||||
public Note GetNoteByVehicleId(int vehicleId)
|
public List<Note> GetNotesByVehicleId(int vehicleId)
|
||||||
{
|
{
|
||||||
using (var db = new LiteDatabase(dbName))
|
using (var db = new LiteDatabase(dbName))
|
||||||
{
|
{
|
||||||
var table = db.GetCollection<Note>(tableName);
|
var table = db.GetCollection<Note>(tableName);
|
||||||
var noteToReturn = table.FindOne(Query.EQ(nameof(Note.VehicleId), vehicleId));
|
var noteToReturn = table.Find(Query.EQ(nameof(Note.VehicleId), vehicleId));
|
||||||
return noteToReturn ?? new Note();
|
return noteToReturn.ToList() ?? new List<Note>();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
public bool SaveNoteToVehicleId(Note note)
|
public Note GetNoteById(int noteId)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<Note>(tableName);
|
||||||
|
return table.FindById(noteId);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public bool SaveNoteToVehicle(Note note)
|
||||||
{
|
{
|
||||||
using (var db = new LiteDatabase(dbName))
|
using (var db = new LiteDatabase(dbName))
|
||||||
{
|
{
|
||||||
@@ -27,12 +35,21 @@ namespace CarCareTracker.External.Implementations
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
public bool DeleteNoteByVehicleId(int vehicleId)
|
public bool DeleteNoteById(int noteId)
|
||||||
{
|
{
|
||||||
using (var db = new LiteDatabase(dbName))
|
using (var db = new LiteDatabase(dbName))
|
||||||
{
|
{
|
||||||
var table = db.GetCollection<Note>(tableName);
|
var table = db.GetCollection<Note>(tableName);
|
||||||
table.DeleteMany(Query.EQ(nameof(Note.VehicleId), vehicleId));
|
table.Delete(noteId);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public bool DeleteAllNotesByVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<Note>(tableName);
|
||||||
|
var notes = table.DeleteMany(Query.EQ(nameof(Note.VehicleId), vehicleId));
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
57
External/Implementations/UpgradeRecordDataAccess.cs
vendored
Normal file
57
External/Implementations/UpgradeRecordDataAccess.cs
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using CarCareTracker.External.Interfaces;
|
||||||
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.Models;
|
||||||
|
using LiteDB;
|
||||||
|
|
||||||
|
namespace CarCareTracker.External.Implementations
|
||||||
|
{
|
||||||
|
public class UpgradeRecordDataAccess : IUpgradeRecordDataAccess
|
||||||
|
{
|
||||||
|
private static string dbName = StaticHelper.DbName;
|
||||||
|
private static string tableName = "upgraderecords";
|
||||||
|
public List<UpgradeRecord> GetUpgradeRecordsByVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<UpgradeRecord>(tableName);
|
||||||
|
var upgradeRecords = table.Find(Query.EQ(nameof(UpgradeRecord.VehicleId), vehicleId));
|
||||||
|
return upgradeRecords.ToList() ?? new List<UpgradeRecord>();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public UpgradeRecord GetUpgradeRecordById(int upgradeRecordId)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<UpgradeRecord>(tableName);
|
||||||
|
return table.FindById(upgradeRecordId);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public bool DeleteUpgradeRecordById(int upgradeRecordId)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<UpgradeRecord>(tableName);
|
||||||
|
table.Delete(upgradeRecordId);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public bool SaveUpgradeRecordToVehicle(UpgradeRecord upgradeRecord)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<UpgradeRecord>(tableName);
|
||||||
|
table.Upsert(upgradeRecord);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public bool DeleteAllUpgradeRecordsByVehicleId(int vehicleId)
|
||||||
|
{
|
||||||
|
using (var db = new LiteDatabase(dbName))
|
||||||
|
{
|
||||||
|
var table = db.GetCollection<UpgradeRecord>(tableName);
|
||||||
|
var upgradeRecords = table.DeleteMany(Query.EQ(nameof(UpgradeRecord.VehicleId), vehicleId));
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
External/Interfaces/INoteDataAccess.cs
vendored
8
External/Interfaces/INoteDataAccess.cs
vendored
@@ -4,8 +4,10 @@ namespace CarCareTracker.External.Interfaces
|
|||||||
{
|
{
|
||||||
public interface INoteDataAccess
|
public interface INoteDataAccess
|
||||||
{
|
{
|
||||||
public Note GetNoteByVehicleId(int vehicleId);
|
public List<Note> GetNotesByVehicleId(int vehicleId);
|
||||||
public bool SaveNoteToVehicleId(Note note);
|
public Note GetNoteById(int noteId);
|
||||||
bool DeleteNoteByVehicleId(int vehicleId);
|
public bool SaveNoteToVehicle(Note note);
|
||||||
|
public bool DeleteNoteById(int noteId);
|
||||||
|
public bool DeleteAllNotesByVehicleId(int vehicleId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13
External/Interfaces/IUpgradeRecordDataAccess.cs
vendored
Normal file
13
External/Interfaces/IUpgradeRecordDataAccess.cs
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using CarCareTracker.Models;
|
||||||
|
|
||||||
|
namespace CarCareTracker.External.Interfaces
|
||||||
|
{
|
||||||
|
public interface IUpgradeRecordDataAccess
|
||||||
|
{
|
||||||
|
public List<UpgradeRecord> GetUpgradeRecordsByVehicleId(int vehicleId);
|
||||||
|
public UpgradeRecord GetUpgradeRecordById(int upgradeRecordId);
|
||||||
|
public bool DeleteUpgradeRecordById(int upgradeRecordId);
|
||||||
|
public bool SaveUpgradeRecordToVehicle(UpgradeRecord upgradeRecord);
|
||||||
|
public bool DeleteAllUpgradeRecordsByVehicleId(int vehicleId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
{
|
{
|
||||||
public interface IFileHelper
|
public interface IFileHelper
|
||||||
{
|
{
|
||||||
string GetFullFilePath(string currentFilePath);
|
string GetFullFilePath(string currentFilePath, bool mustExist = true);
|
||||||
public string MoveFileFromTemp(string currentFilePath, string newFolder);
|
string MoveFileFromTemp(string currentFilePath, string newFolder);
|
||||||
public bool DeleteFile(string currentFilePath);
|
bool DeleteFile(string currentFilePath);
|
||||||
}
|
}
|
||||||
public class FileHelper: IFileHelper
|
public class FileHelper: IFileHelper
|
||||||
{
|
{
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
{
|
{
|
||||||
_webEnv = webEnv;
|
_webEnv = webEnv;
|
||||||
}
|
}
|
||||||
public string GetFullFilePath(string currentFilePath)
|
public string GetFullFilePath(string currentFilePath, bool mustExist = true)
|
||||||
{
|
{
|
||||||
if (currentFilePath.StartsWith("/"))
|
if (currentFilePath.StartsWith("/"))
|
||||||
{
|
{
|
||||||
@@ -23,7 +23,10 @@
|
|||||||
if (File.Exists(oldFilePath))
|
if (File.Exists(oldFilePath))
|
||||||
{
|
{
|
||||||
return oldFilePath;
|
return oldFilePath;
|
||||||
} else
|
} else if (!mustExist)
|
||||||
|
{
|
||||||
|
return oldFilePath;
|
||||||
|
}
|
||||||
{
|
{
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|||||||
90
Helper/GasHelper.cs
Normal file
90
Helper/GasHelper.cs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
using CarCareTracker.Models;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Helper
|
||||||
|
{
|
||||||
|
public interface IGasHelper
|
||||||
|
{
|
||||||
|
List<GasRecordViewModel> GetGasRecordViewModels(List<GasRecord> result, bool useMPG, bool useUKMPG);
|
||||||
|
}
|
||||||
|
public class GasHelper : IGasHelper
|
||||||
|
{
|
||||||
|
public List<GasRecordViewModel> GetGasRecordViewModels(List<GasRecord> result, bool useMPG, bool useUKMPG)
|
||||||
|
{
|
||||||
|
var computedResults = new List<GasRecordViewModel>();
|
||||||
|
int previousMileage = 0;
|
||||||
|
decimal unFactoredConsumption = 0.00M;
|
||||||
|
int unFactoredMileage = 0;
|
||||||
|
//perform computation.
|
||||||
|
for (int i = 0; i < result.Count; i++)
|
||||||
|
{
|
||||||
|
var currentObject = result[i];
|
||||||
|
decimal convertedConsumption;
|
||||||
|
if (useUKMPG && useMPG)
|
||||||
|
{
|
||||||
|
//if we're using UK MPG and the user wants imperial calculation insteace of l/100km
|
||||||
|
//if UK MPG is selected then the gas consumption are stored in liters but need to convert into UK gallons for computation.
|
||||||
|
convertedConsumption = currentObject.Gallons / 4.546M;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
convertedConsumption = currentObject.Gallons;
|
||||||
|
}
|
||||||
|
if (i > 0)
|
||||||
|
{
|
||||||
|
var deltaMileage = currentObject.Mileage - previousMileage;
|
||||||
|
var gasRecordViewModel = new GasRecordViewModel()
|
||||||
|
{
|
||||||
|
Id = currentObject.Id,
|
||||||
|
VehicleId = currentObject.VehicleId,
|
||||||
|
Date = currentObject.Date.ToShortDateString(),
|
||||||
|
Mileage = currentObject.Mileage,
|
||||||
|
Gallons = convertedConsumption,
|
||||||
|
Cost = currentObject.Cost,
|
||||||
|
DeltaMileage = deltaMileage,
|
||||||
|
CostPerGallon = currentObject.Cost / convertedConsumption
|
||||||
|
};
|
||||||
|
if (currentObject.MissedFuelUp)
|
||||||
|
{
|
||||||
|
//if they missed a fuel up, we skip MPG calculation.
|
||||||
|
gasRecordViewModel.MilesPerGallon = 0;
|
||||||
|
//reset unFactored vars for missed fuel up because the numbers wont be reliable.
|
||||||
|
unFactoredConsumption = 0;
|
||||||
|
unFactoredMileage = 0;
|
||||||
|
}
|
||||||
|
else if (currentObject.IsFillToFull)
|
||||||
|
{
|
||||||
|
//if user filled to full.
|
||||||
|
gasRecordViewModel.MilesPerGallon = useMPG ? (unFactoredMileage + deltaMileage) / (unFactoredConsumption + convertedConsumption) : 100 / ((unFactoredMileage + deltaMileage) / (unFactoredConsumption + convertedConsumption));
|
||||||
|
//reset unFactored vars
|
||||||
|
unFactoredConsumption = 0;
|
||||||
|
unFactoredMileage = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
unFactoredConsumption += convertedConsumption;
|
||||||
|
unFactoredMileage += deltaMileage;
|
||||||
|
gasRecordViewModel.MilesPerGallon = 0;
|
||||||
|
}
|
||||||
|
computedResults.Add(gasRecordViewModel);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
computedResults.Add(new GasRecordViewModel()
|
||||||
|
{
|
||||||
|
Id = currentObject.Id,
|
||||||
|
VehicleId = currentObject.VehicleId,
|
||||||
|
Date = currentObject.Date.ToShortDateString(),
|
||||||
|
Mileage = currentObject.Mileage,
|
||||||
|
Gallons = convertedConsumption,
|
||||||
|
Cost = currentObject.Cost,
|
||||||
|
DeltaMileage = 0,
|
||||||
|
MilesPerGallon = 0,
|
||||||
|
CostPerGallon = currentObject.Cost / convertedConsumption
|
||||||
|
});
|
||||||
|
}
|
||||||
|
previousMileage = currentObject.Mileage;
|
||||||
|
}
|
||||||
|
return computedResults;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
47
Helper/LoginHelper.cs
Normal file
47
Helper/LoginHelper.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using CarCareTracker.Models;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Helper
|
||||||
|
{
|
||||||
|
public interface ILoginHelper
|
||||||
|
{
|
||||||
|
bool ValidateUserCredentials(LoginModel credentials);
|
||||||
|
}
|
||||||
|
public class LoginHelper: ILoginHelper
|
||||||
|
{
|
||||||
|
public bool ValidateUserCredentials(LoginModel credentials)
|
||||||
|
{
|
||||||
|
var configFileContents = System.IO.File.ReadAllText(StaticHelper.UserConfigPath);
|
||||||
|
var existingUserConfig = System.Text.Json.JsonSerializer.Deserialize<UserConfig>(configFileContents);
|
||||||
|
if (existingUserConfig is not null)
|
||||||
|
{
|
||||||
|
//create hashes of the login credentials.
|
||||||
|
var hashedUserName = Sha256_hash(credentials.UserName);
|
||||||
|
var hashedPassword = Sha256_hash(credentials.Password);
|
||||||
|
//compare against stored hash.
|
||||||
|
if (hashedUserName == existingUserConfig.UserNameHash &&
|
||||||
|
hashedPassword == existingUserConfig.UserPasswordHash)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
private static string Sha256_hash(string value)
|
||||||
|
{
|
||||||
|
StringBuilder Sb = new StringBuilder();
|
||||||
|
|
||||||
|
using (var hash = SHA256.Create())
|
||||||
|
{
|
||||||
|
Encoding enc = Encoding.UTF8;
|
||||||
|
byte[] result = hash.ComputeHash(enc.GetBytes(value));
|
||||||
|
|
||||||
|
foreach (byte b in result)
|
||||||
|
Sb.Append(b.ToString("x2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
97
Helper/ReminderHelper.cs
Normal file
97
Helper/ReminderHelper.cs
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
using CarCareTracker.Models;
|
||||||
|
|
||||||
|
namespace CarCareTracker.Helper
|
||||||
|
{
|
||||||
|
public interface IReminderHelper
|
||||||
|
{
|
||||||
|
List<ReminderRecordViewModel> GetReminderRecordViewModels(List<ReminderRecord> reminders, int currentMileage);
|
||||||
|
}
|
||||||
|
public class ReminderHelper: IReminderHelper
|
||||||
|
{
|
||||||
|
public List<ReminderRecordViewModel> GetReminderRecordViewModels(List<ReminderRecord> reminders, int currentMileage)
|
||||||
|
{
|
||||||
|
List<ReminderRecordViewModel> reminderViewModels = new List<ReminderRecordViewModel>();
|
||||||
|
foreach (var reminder in reminders)
|
||||||
|
{
|
||||||
|
var reminderViewModel = new ReminderRecordViewModel()
|
||||||
|
{
|
||||||
|
Id = reminder.Id,
|
||||||
|
VehicleId = reminder.VehicleId,
|
||||||
|
Date = reminder.Date,
|
||||||
|
Mileage = reminder.Mileage,
|
||||||
|
Description = reminder.Description,
|
||||||
|
Notes = reminder.Notes,
|
||||||
|
Metric = reminder.Metric
|
||||||
|
};
|
||||||
|
if (reminder.Metric == ReminderMetric.Both)
|
||||||
|
{
|
||||||
|
if (reminder.Date < DateTime.Now)
|
||||||
|
{
|
||||||
|
reminderViewModel.Urgency = ReminderUrgency.PastDue;
|
||||||
|
reminderViewModel.Metric = ReminderMetric.Date;
|
||||||
|
}
|
||||||
|
else if (reminder.Mileage < currentMileage)
|
||||||
|
{
|
||||||
|
reminderViewModel.Urgency = ReminderUrgency.PastDue;
|
||||||
|
reminderViewModel.Metric = ReminderMetric.Odometer;
|
||||||
|
}
|
||||||
|
else if (reminder.Date < DateTime.Now.AddDays(7))
|
||||||
|
{
|
||||||
|
//if less than a week from today or less than 50 miles from current mileage then very urgent.
|
||||||
|
reminderViewModel.Urgency = ReminderUrgency.VeryUrgent;
|
||||||
|
//have to specify by which metric this reminder is urgent.
|
||||||
|
reminderViewModel.Metric = ReminderMetric.Date;
|
||||||
|
}
|
||||||
|
else if (reminder.Mileage < currentMileage + 50)
|
||||||
|
{
|
||||||
|
reminderViewModel.Urgency = ReminderUrgency.VeryUrgent;
|
||||||
|
reminderViewModel.Metric = ReminderMetric.Odometer;
|
||||||
|
}
|
||||||
|
else if (reminder.Date < DateTime.Now.AddDays(30))
|
||||||
|
{
|
||||||
|
reminderViewModel.Urgency = ReminderUrgency.Urgent;
|
||||||
|
reminderViewModel.Metric = ReminderMetric.Date;
|
||||||
|
}
|
||||||
|
else if (reminder.Mileage < currentMileage + 100)
|
||||||
|
{
|
||||||
|
reminderViewModel.Urgency = ReminderUrgency.Urgent;
|
||||||
|
reminderViewModel.Metric = ReminderMetric.Odometer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (reminder.Metric == ReminderMetric.Date)
|
||||||
|
{
|
||||||
|
if (reminder.Date < DateTime.Now)
|
||||||
|
{
|
||||||
|
reminderViewModel.Urgency = ReminderUrgency.PastDue;
|
||||||
|
}
|
||||||
|
else if (reminder.Date < DateTime.Now.AddDays(7))
|
||||||
|
{
|
||||||
|
reminderViewModel.Urgency = ReminderUrgency.VeryUrgent;
|
||||||
|
}
|
||||||
|
else if (reminder.Date < DateTime.Now.AddDays(30))
|
||||||
|
{
|
||||||
|
reminderViewModel.Urgency = ReminderUrgency.Urgent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (reminder.Metric == ReminderMetric.Odometer)
|
||||||
|
{
|
||||||
|
if (reminder.Mileage < currentMileage)
|
||||||
|
{
|
||||||
|
reminderViewModel.Urgency = ReminderUrgency.PastDue;
|
||||||
|
reminderViewModel.Metric = ReminderMetric.Odometer;
|
||||||
|
}
|
||||||
|
else if (reminder.Mileage < currentMileage + 50)
|
||||||
|
{
|
||||||
|
reminderViewModel.Urgency = ReminderUrgency.VeryUrgent;
|
||||||
|
}
|
||||||
|
else if (reminder.Mileage < currentMileage + 100)
|
||||||
|
{
|
||||||
|
reminderViewModel.Urgency = ReminderUrgency.Urgent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reminderViewModels.Add(reminderViewModel);
|
||||||
|
}
|
||||||
|
return reminderViewModels;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,5 +7,20 @@
|
|||||||
{
|
{
|
||||||
public static string DbName = "data/cartracker.db";
|
public static string DbName = "data/cartracker.db";
|
||||||
public static string UserConfigPath = "config/userConfig.json";
|
public static string UserConfigPath = "config/userConfig.json";
|
||||||
|
|
||||||
|
public static string TruncateStrings(string input, int maxLength = 25)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(input))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
if (input.Length > maxLength)
|
||||||
|
{
|
||||||
|
return (input.Substring(0, maxLength) + "...");
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using CarCareTracker.Models;
|
using CarCareTracker.Helper;
|
||||||
|
using CarCareTracker.Models;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Components.Web;
|
using Microsoft.AspNetCore.Components.Web;
|
||||||
using Microsoft.AspNetCore.DataProtection;
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
@@ -14,17 +15,20 @@ namespace CarCareTracker.Middleware
|
|||||||
{
|
{
|
||||||
private IHttpContextAccessor _httpContext;
|
private IHttpContextAccessor _httpContext;
|
||||||
private IDataProtector _dataProtector;
|
private IDataProtector _dataProtector;
|
||||||
|
private ILoginHelper _loginHelper;
|
||||||
private bool enableAuth;
|
private bool enableAuth;
|
||||||
public Authen(
|
public Authen(
|
||||||
IOptionsMonitor<AuthenticationSchemeOptions> options,
|
IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||||
UrlEncoder encoder,
|
UrlEncoder encoder,
|
||||||
ILoggerFactory logger,
|
ILoggerFactory logger,
|
||||||
IConfiguration configuration,
|
IConfiguration configuration,
|
||||||
|
ILoginHelper loginHelper,
|
||||||
IDataProtectionProvider securityProvider,
|
IDataProtectionProvider securityProvider,
|
||||||
IHttpContextAccessor httpContext) : base(options, logger, encoder)
|
IHttpContextAccessor httpContext) : base(options, logger, encoder)
|
||||||
{
|
{
|
||||||
_httpContext = httpContext;
|
_httpContext = httpContext;
|
||||||
_dataProtector = securityProvider.CreateProtector("login");
|
_dataProtector = securityProvider.CreateProtector("login");
|
||||||
|
_loginHelper = loginHelper;
|
||||||
enableAuth = bool.Parse(configuration["EnableAuth"]);
|
enableAuth = bool.Parse(configuration["EnableAuth"]);
|
||||||
}
|
}
|
||||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||||
@@ -45,11 +49,38 @@ namespace CarCareTracker.Middleware
|
|||||||
{
|
{
|
||||||
//auth is enabled by user, we will have to authenticate the user via a ticket retrieved from the auth cookie.
|
//auth is enabled by user, we will have to authenticate the user via a ticket retrieved from the auth cookie.
|
||||||
var access_token = _httpContext.HttpContext.Request.Cookies["ACCESS_TOKEN"];
|
var access_token = _httpContext.HttpContext.Request.Cookies["ACCESS_TOKEN"];
|
||||||
if (string.IsNullOrWhiteSpace(access_token))
|
//auth using Basic Auth for API.
|
||||||
|
var request_header = _httpContext.HttpContext.Request.Headers["Authorization"];
|
||||||
|
if (string.IsNullOrWhiteSpace(access_token) && string.IsNullOrWhiteSpace(request_header))
|
||||||
{
|
{
|
||||||
return AuthenticateResult.Fail("Cookie is invalid or does not exist.");
|
return AuthenticateResult.Fail("Cookie is invalid or does not exist.");
|
||||||
}
|
}
|
||||||
else
|
else if (!string.IsNullOrWhiteSpace(request_header))
|
||||||
|
{
|
||||||
|
var cleanedHeader = request_header.ToString().Replace("Basic ", "").Trim();
|
||||||
|
byte[] data = Convert.FromBase64String(cleanedHeader);
|
||||||
|
string decodedString = System.Text.Encoding.UTF8.GetString(data);
|
||||||
|
var splitString = decodedString.Split(":");
|
||||||
|
if (splitString.Count() != 2)
|
||||||
|
{
|
||||||
|
return AuthenticateResult.Fail("Invalid credentials");
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
var validUser = _loginHelper.ValidateUserCredentials(new LoginModel { UserName = splitString[0], Password = splitString[1] });
|
||||||
|
if (validUser)
|
||||||
|
{
|
||||||
|
var appIdentity = new ClaimsIdentity("Custom");
|
||||||
|
var userIdentity = new List<Claim>
|
||||||
|
{
|
||||||
|
new(ClaimTypes.Name, splitString[0])
|
||||||
|
};
|
||||||
|
appIdentity.AddClaims(userIdentity);
|
||||||
|
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), this.Scheme.Name);
|
||||||
|
return AuthenticateResult.Success(ticket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrWhiteSpace(access_token))
|
||||||
{
|
{
|
||||||
//decrypt the access token.
|
//decrypt the access token.
|
||||||
var decryptedCookie = _dataProtector.Unprotect(access_token);
|
var decryptedCookie = _dataProtector.Unprotect(access_token);
|
||||||
@@ -84,6 +115,14 @@ namespace CarCareTracker.Middleware
|
|||||||
}
|
}
|
||||||
protected override Task HandleChallengeAsync(AuthenticationProperties properties)
|
protected override Task HandleChallengeAsync(AuthenticationProperties properties)
|
||||||
{
|
{
|
||||||
|
if (Request.RouteValues.TryGetValue("controller", out object value))
|
||||||
|
{
|
||||||
|
if (value.ToString().ToLower() == "api")
|
||||||
|
{
|
||||||
|
Response.StatusCode = 401;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
Response.Redirect("/Login/Index");
|
Response.Redirect("/Login/Index");
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
public decimal Gallons { get; set; }
|
public decimal Gallons { get; set; }
|
||||||
public decimal Cost { get; set; }
|
public decimal Cost { get; set; }
|
||||||
public bool IsFillToFull { get; set; } = true;
|
public bool IsFillToFull { get; set; } = true;
|
||||||
|
public bool MissedFuelUp { get; set; } = false;
|
||||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
public decimal Gallons { get; set; }
|
public decimal Gallons { get; set; }
|
||||||
public decimal Cost { get; set; }
|
public decimal Cost { get; set; }
|
||||||
public bool IsFillToFull { get; set; } = true;
|
public bool IsFillToFull { get; set; } = true;
|
||||||
|
public bool MissedFuelUp { get; set; } = false;
|
||||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||||
public GasRecord ToGasRecord() { return new GasRecord {
|
public GasRecord ToGasRecord() { return new GasRecord {
|
||||||
Id = Id,
|
Id = Id,
|
||||||
@@ -24,7 +25,8 @@
|
|||||||
Mileage = Mileage,
|
Mileage = Mileage,
|
||||||
VehicleId = VehicleId,
|
VehicleId = VehicleId,
|
||||||
Files = Files,
|
Files = Files,
|
||||||
IsFillToFull = IsFillToFull
|
IsFillToFull = IsFillToFull,
|
||||||
|
MissedFuelUp = MissedFuelUp
|
||||||
}; }
|
}; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,4 +15,35 @@
|
|||||||
public string PartialFuelUp { get; set; }
|
public string PartialFuelUp { get; set; }
|
||||||
public string IsFillToFull { get; set; }
|
public string IsFillToFull { get; set; }
|
||||||
}
|
}
|
||||||
|
public class ServiceRecordExportModel
|
||||||
|
{
|
||||||
|
public string Date { get; set; }
|
||||||
|
public string Odometer { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string Notes { get; set; }
|
||||||
|
public string Cost { get; set; }
|
||||||
|
}
|
||||||
|
public class TaxRecordExportModel
|
||||||
|
{
|
||||||
|
public string Date { get; set; }
|
||||||
|
public string Odometer { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string Notes { get; set; }
|
||||||
|
public string Cost { get; set; }
|
||||||
|
}
|
||||||
|
public class GasRecordExportModel
|
||||||
|
{
|
||||||
|
public string Date { get; set; }
|
||||||
|
public string Odometer { get; set; }
|
||||||
|
public string FuelConsumed { get; set; }
|
||||||
|
public string Cost { get; set; }
|
||||||
|
public string FuelEconomy { get; set; }
|
||||||
|
}
|
||||||
|
public class ReminderExportModel
|
||||||
|
{
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string Urgency { get; set; }
|
||||||
|
public string Metric { get; set; }
|
||||||
|
public string Notes { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public int VehicleId { get; set; }
|
public int VehicleId { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
public string NoteText { get; set; }
|
public string NoteText { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,5 +6,6 @@
|
|||||||
public decimal GasRecordSum { get; set; }
|
public decimal GasRecordSum { get; set; }
|
||||||
public decimal TaxRecordSum { get; set; }
|
public decimal TaxRecordSum { get; set; }
|
||||||
public decimal CollisionRecordSum { get; set; }
|
public decimal CollisionRecordSum { get; set; }
|
||||||
|
public decimal UpgradeRecordSum { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
14
Models/Upgrades/UpgradeRecord.cs
Normal file
14
Models/Upgrades/UpgradeRecord.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class UpgradeRecord
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int VehicleId { get; set; }
|
||||||
|
public DateTime Date { get; set; }
|
||||||
|
public int Mileage { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public decimal Cost { get; set; }
|
||||||
|
public string Notes { get; set; }
|
||||||
|
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||||
|
}
|
||||||
|
}
|
||||||
15
Models/Upgrades/UpgradeReportInput.cs
Normal file
15
Models/Upgrades/UpgradeReportInput.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
namespace CarCareTracker.Models
|
||||||
|
{
|
||||||
|
public class UpgradeRecordInput
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int VehicleId { get; set; }
|
||||||
|
public string Date { get; set; }
|
||||||
|
public int Mileage { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public decimal Cost { get; set; }
|
||||||
|
public string Notes { get; set; }
|
||||||
|
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||||
|
public UpgradeRecord ToUpgradeRecord() { return new UpgradeRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Cost = Cost, Mileage = Mileage, Description = Description, Notes = Notes, Files = Files }; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,13 @@ builder.Services.AddSingleton<IGasRecordDataAccess, GasRecordDataAccess>();
|
|||||||
builder.Services.AddSingleton<ICollisionRecordDataAccess, CollisionRecordDataAccess>();
|
builder.Services.AddSingleton<ICollisionRecordDataAccess, CollisionRecordDataAccess>();
|
||||||
builder.Services.AddSingleton<ITaxRecordDataAccess, TaxRecordDataAccess>();
|
builder.Services.AddSingleton<ITaxRecordDataAccess, TaxRecordDataAccess>();
|
||||||
builder.Services.AddSingleton<IReminderRecordDataAccess, ReminderRecordDataAccess>();
|
builder.Services.AddSingleton<IReminderRecordDataAccess, ReminderRecordDataAccess>();
|
||||||
|
builder.Services.AddSingleton<IUpgradeRecordDataAccess, UpgradeRecordDataAccess>();
|
||||||
|
|
||||||
|
//configure helpers
|
||||||
builder.Services.AddSingleton<IFileHelper, FileHelper>();
|
builder.Services.AddSingleton<IFileHelper, FileHelper>();
|
||||||
|
builder.Services.AddSingleton<IGasHelper, GasHelper>();
|
||||||
|
builder.Services.AddSingleton<IReminderHelper, ReminderHelper>();
|
||||||
|
builder.Services.AddSingleton<ILoginHelper, LoginHelper>();
|
||||||
|
|
||||||
if (!Directory.Exists("data"))
|
if (!Directory.Exists("data"))
|
||||||
{
|
{
|
||||||
|
|||||||
127
Views/API/Index.cshtml
Normal file
127
Views/API/Index.cshtml
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
<div class="row">
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<h6 class="display-6 mt-2">API</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<p class="lead">If authentication is enabled, use the credentials of the user for Basic Auth(RFC2617)</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-1">
|
||||||
|
<h6>Method</h6>
|
||||||
|
</div>
|
||||||
|
<div class="col-5">
|
||||||
|
<h6>Endpoint</h6>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
<h6>Description</h6>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
<h6>Parameters</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-1">
|
||||||
|
GET
|
||||||
|
</div>
|
||||||
|
<div class="col-5">
|
||||||
|
<code>/api/vehicles</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Returns a list of vehicles
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
No Params
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-1">
|
||||||
|
GET
|
||||||
|
</div>
|
||||||
|
<div class="col-5">
|
||||||
|
<code>/api/vehicle/servicerecords</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Returns a list of service records for the vehicle
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
vehicleId - Id of Vehicle
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-1">
|
||||||
|
GET
|
||||||
|
</div>
|
||||||
|
<div class="col-5">
|
||||||
|
<code>/api/vehicle/repairrecords</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Returns a list of repair records for the vehicle
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
vehicleId - Id of Vehicle
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-1">
|
||||||
|
GET
|
||||||
|
</div>
|
||||||
|
<div class="col-5">
|
||||||
|
<code>/api/vehicle/upgraderecords</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Returns a list of upgrade records for the vehicle
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
vehicleId - Id of Vehicle
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-1">
|
||||||
|
GET
|
||||||
|
</div>
|
||||||
|
<div class="col-5">
|
||||||
|
<code>/api/vehicle/taxrecords</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Returns a list of tax records for the vehicle
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
vehicleId - Id of Vehicle
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-1">
|
||||||
|
GET
|
||||||
|
</div>
|
||||||
|
<div class="col-5">
|
||||||
|
<code>/api/vehicle/gasrecords</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Returns a list of gas records for the vehicle
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
vehicleId - Id of Vehicle
|
||||||
|
<br />
|
||||||
|
useMPG(bool) - Use Imperial Units and Calculation
|
||||||
|
<br />
|
||||||
|
useUKMPG(bool) - Use UK Imperial Calculation
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-1">
|
||||||
|
GET
|
||||||
|
</div>
|
||||||
|
<div class="col-5">
|
||||||
|
<code>/api/vehicle/reminders</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
Returns a list of reminders for the vehicle
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
vehicleId - Id of Vehicle
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -9,6 +9,8 @@
|
|||||||
<script src="~/js/collisionrecord.js" asp-append-version="true"></script>
|
<script src="~/js/collisionrecord.js" asp-append-version="true"></script>
|
||||||
<script src="~/js/taxrecord.js" asp-append-version="true"></script>
|
<script src="~/js/taxrecord.js" asp-append-version="true"></script>
|
||||||
<script src="~/js/reminderrecord.js" asp-append-version="true"></script>
|
<script src="~/js/reminderrecord.js" asp-append-version="true"></script>
|
||||||
|
<script src="~/js/upgraderecord.js" asp-append-version="true"></script>
|
||||||
|
<script src="~/js/note.js" asp-append-version="true"></script>
|
||||||
<script src="~/lib/chart-js/chart.umd.js"></script>
|
<script src="~/lib/chart-js/chart.umd.js"></script>
|
||||||
}
|
}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@@ -27,6 +29,9 @@
|
|||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link" id="accident-tab" data-bs-toggle="tab" data-bs-target="#accident-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-exclamation-octagon me-2"></i>Repairs</button>
|
<button class="nav-link" id="accident-tab" data-bs-toggle="tab" data-bs-target="#accident-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-exclamation-octagon me-2"></i>Repairs</button>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="upgrade-tab" data-bs-toggle="tab" data-bs-target="#upgrade-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-wrench-adjustable me-2"></i>Upgrades</button>
|
||||||
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link" id="gas-tab" data-bs-toggle="tab" data-bs-target="#gas-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-fuel-pump me-2"></i>Fuel</button>
|
<button class="nav-link" id="gas-tab" data-bs-toggle="tab" data-bs-target="#gas-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-fuel-pump me-2"></i>Fuel</button>
|
||||||
</li>
|
</li>
|
||||||
@@ -53,24 +58,11 @@
|
|||||||
<div class="tab-pane fade show active" id="servicerecord-tab-pane" role="tabpanel" tabindex="0"></div>
|
<div class="tab-pane fade show active" id="servicerecord-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||||
<div class="tab-pane fade" id="gas-tab-pane" role="tabpanel" tabindex="0"></div>
|
<div class="tab-pane fade" id="gas-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||||
<div class="tab-pane fade" id="tax-tab-pane" role="tabpanel" tabindex="0"></div>
|
<div class="tab-pane fade" id="tax-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||||
<div class="tab-pane fade" id="notes-tab-pane" role="tabpanel" tabindex="0">
|
<div class="tab-pane fade" id="notes-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||||
<div class="row">
|
|
||||||
<div class="col-12">
|
|
||||||
<label for="noteTextArea" class="form-label">This is where you can store notes related to the vehicle such as tire size, oil filter size, oil types, etc.</label>
|
|
||||||
<textarea class="form-control vehicleNoteContainer" id="noteTextArea"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="d-flex flex-row-reverse">
|
|
||||||
<div>
|
|
||||||
<button onclick="saveVehicleNote(@Model.Id)" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square"></i>Save Note</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane fade" id="accident-tab-pane" role="tabpanel" tabindex="0"></div>
|
<div class="tab-pane fade" id="accident-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||||
<div class="tab-pane fade" id="reminder-tab-pane" role="tabpanel" tabindex="0"></div>
|
<div class="tab-pane fade" id="reminder-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||||
<div class="tab-pane fade" id="report-tab-pane" role="tabpanel" tabindex="0"></div>
|
<div class="tab-pane fade" id="report-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||||
|
<div class="tab-pane fade" id="upgrade-tab-pane" role="tabpanel" tabindex="0"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal fade" id="editVehicleModal" tabindex="-1" role="dialog">
|
<div class="modal fade" id="editVehicleModal" tabindex="-1" role="dialog">
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
@if (Model == ImportMode.GasRecord)
|
@if (Model == ImportMode.GasRecord)
|
||||||
{
|
{
|
||||||
<a class="btn btn-link" href="/defaults/gassample.csv" target="_blank">Download Sample</a>
|
<a class="btn btn-link" href="/defaults/gassample.csv" target="_blank">Download Sample</a>
|
||||||
} else if (Model == ImportMode.ServiceRecord || Model == ImportMode.RepairRecord)
|
} else if (Model == ImportMode.ServiceRecord || Model == ImportMode.RepairRecord || Model == ImportMode.UpgradeRecord)
|
||||||
{
|
{
|
||||||
<a class="btn btn-link" href="/defaults/servicerecordsample.csv" target="_blank">Download Sample</a>
|
<a class="btn btn-link" href="/defaults/servicerecordsample.csv" target="_blank">Download Sample</a>
|
||||||
} else if (Model == ImportMode.TaxRecord)
|
} else if (Model == ImportMode.TaxRecord)
|
||||||
@@ -60,6 +60,8 @@
|
|||||||
getVehicleCollisionRecords(vehicleId);
|
getVehicleCollisionRecords(vehicleId);
|
||||||
} else if (mode == "TaxRecord") {
|
} else if (mode == "TaxRecord") {
|
||||||
getVehicleTaxRecords(vehicleId);
|
getVehicleTaxRecords(vehicleId);
|
||||||
|
} else if (mode == "UpgradeRecord") {
|
||||||
|
getVehicleUpgradeRecords(vehicleId);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errorToast("An error has occurred, please double check the data and try again.");
|
errorToast("An error has occurred, please double check the data and try again.");
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<label for="collisionRecordDescription">Description</label>
|
<label for="collisionRecordDescription">Description</label>
|
||||||
<input type="text" id="collisionRecordDescription" class="form-control" placeholder="Description of item(s) repaired(i.e. Alternator)" value="@Model.Description">
|
<input type="text" id="collisionRecordDescription" class="form-control" placeholder="Description of item(s) repaired(i.e. Alternator)" value="@Model.Description">
|
||||||
<label for="collisionRecordCost">Cost</label>
|
<label for="collisionRecordCost">Cost</label>
|
||||||
<input type="number" id="collisionRecordCost" class="form-control" placeholder="Cost of the repair" value="@(isNew ? "" : Model.Cost)">
|
<input type="text" id="collisionRecordCost" class="form-control" placeholder="Cost of the repair" value="@(isNew ? "" : Model.Cost)">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-12">
|
<div class="col-md-6 col-12">
|
||||||
<label for="collisionRecordNotes">Notes(optional)</label>
|
<label for="collisionRecordNotes">Notes(optional)</label>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('RepairRecord')">Import via CSV</a></li>
|
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('RepairRecord')">Import via CSV</a></li>
|
||||||
|
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('RepairRecord')">Export to CSV</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -50,7 +51,7 @@
|
|||||||
<td class="col-2">@collisionRecord.Mileage</td>
|
<td class="col-2">@collisionRecord.Mileage</td>
|
||||||
<td class="col-4">@collisionRecord.Description</td>
|
<td class="col-4">@collisionRecord.Description</td>
|
||||||
<td class="col-2">@((hideZero && collisionRecord.Cost == default) ? "---" : collisionRecord.Cost.ToString("C"))</td>
|
<td class="col-2">@((hideZero && collisionRecord.Cost == default) ? "---" : collisionRecord.Cost.ToString("C"))</td>
|
||||||
<td class="col-3 text-truncate">@collisionRecord.Notes</td>
|
<td class="col-3 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(collisionRecord.Notes)</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
@model CostMakeUpForVehicle
|
@model CostMakeUpForVehicle
|
||||||
@if (Model.CollisionRecordSum + Model.ServiceRecordSum + Model.GasRecordSum + Model.TaxRecordSum > 0)
|
@if (Model.CollisionRecordSum + Model.ServiceRecordSum + Model.GasRecordSum + Model.TaxRecordSum + Model.UpgradeRecordSum > 0)
|
||||||
{
|
{
|
||||||
<canvas id="pie-chart"></canvas>
|
<canvas id="pie-chart"></canvas>
|
||||||
<script>
|
<script>
|
||||||
@@ -9,17 +9,18 @@
|
|||||||
new Chart($("#pie-chart"), {
|
new Chart($("#pie-chart"), {
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
data: {
|
data: {
|
||||||
labels: ["Planned Maintenance(Service Records)", "Unplanned Maintenance(Repairs)", "Tax", "Fuel"],
|
labels: ["Planned Maintenance(Service Records)", "Unplanned Maintenance(Repairs)", "Upgrades", "Tax", "Fuel"],
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: "Expenses by Category",
|
label: "Expenses by Category",
|
||||||
backgroundColor: ["#003f5c", "#58508d", "#bc5090", "#ff6361"],
|
backgroundColor: ["#003f5c", "#58508d", "#bc5090", "#ff6361", "#ffa600"],
|
||||||
data: [
|
data: [
|
||||||
@Model.ServiceRecordSum,
|
@Model.ServiceRecordSum,
|
||||||
@Model.CollisionRecordSum,
|
@Model.CollisionRecordSum,
|
||||||
|
@Model.UpgradeRecordSum,
|
||||||
@Model.TaxRecordSum,
|
@Model.TaxRecordSum,
|
||||||
@Model.GasRecordSum
|
@Model.GasRecordSum
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -41,7 +42,8 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
<h4>No data found or all records have zero sums, insert records with non-zero sums to see visualizations here.</h4>
|
<h4>No data found or all records have zero sums, insert records with non-zero sums to see visualizations here.</h4>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('GasRecord')">Import via CSV</a></li>
|
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('GasRecord')">Import via CSV</a></li>
|
||||||
|
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('GasRecord')">Export to CSV</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -50,8 +50,12 @@
|
|||||||
<input class="form-check-input" type="checkbox" role="switch" id="gasIsFillToFull" checked="@Model.GasRecord.IsFillToFull">
|
<input class="form-check-input" type="checkbox" role="switch" id="gasIsFillToFull" checked="@Model.GasRecord.IsFillToFull">
|
||||||
<label class="form-check-label" for="gasIsFillToFull">Is Filled To Full</label>
|
<label class="form-check-label" for="gasIsFillToFull">Is Filled To Full</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" role="switch" id="gasIsMissed" checked="@Model.GasRecord.MissedFuelUp">
|
||||||
|
<label class="form-check-label" for="gasIsMissed">Missed Fuel Up(Skip MPG Calculation)</label>
|
||||||
|
</div>
|
||||||
<label for="GasRecordCost">Cost</label>
|
<label for="GasRecordCost">Cost</label>
|
||||||
<input type="number" id="gasRecordCost" class="form-control" placeholder="Cost of gas refueled" value="@(isNew ? "" : Model.GasRecord.Cost)">
|
<input type="text" id="gasRecordCost" class="form-control" placeholder="Cost of gas refueled" value="@(isNew ? "" : Model.GasRecord.Cost)">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-12">
|
<div class="col-md-6 col-12">
|
||||||
@if (Model.GasRecord.Files.Any())
|
@if (Model.GasRecord.Files.Any())
|
||||||
|
|||||||
45
Views/Vehicle/_NoteModal.cshtml
Normal file
45
Views/Vehicle/_NoteModal.cshtml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
@model Note
|
||||||
|
@{
|
||||||
|
var isNew = Model.Id == 0;
|
||||||
|
}
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">@(isNew ? "Add New Note" : "Edit Note")</h5>
|
||||||
|
<button type="button" class="btn-close" onclick="hideAddNoteModal()" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
|
||||||
|
<label for="noteDescription">Description</label>
|
||||||
|
<input type="text" id="noteDescription" class="form-control" placeholder="Description of the note" value="@(isNew ? "" : Model.Description)">
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<label for="noteTextArea">Notes</label>
|
||||||
|
<textarea class="form-control vehicleNoteContainer" id="noteTextArea">@Model.NoteText</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
@if (!isNew)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-danger" onclick="deleteNote(@Model.Id)" style="margin-right:auto;">Delete</button>
|
||||||
|
}
|
||||||
|
<button type="button" class="btn btn-secondary" onclick="hideAddNoteModal()">Cancel</button>
|
||||||
|
@if (isNew)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-primary" onclick="saveNoteToVehicle()">Add New Note</button>
|
||||||
|
}
|
||||||
|
else if (!isNew)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-primary" onclick="saveNoteToVehicle(true)">Edit Note</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
function getNoteModelData(){
|
||||||
|
return { id: @Model.Id}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
40
Views/Vehicle/_Notes.cshtml
Normal file
40
Views/Vehicle/_Notes.cshtml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
@model List<Note>
|
||||||
|
<div class="row">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<div class="d-flex align-items-center flex-wrap">
|
||||||
|
<span class="ms-2 badge bg-success">@($"# of Notes: {Model.Count()}")</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button onclick="showAddNoteModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Note</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row vehicleDetailTabContainer">
|
||||||
|
<div class="col-12">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr class="d-flex">
|
||||||
|
<th scope="col" class="col-3">Description</th>
|
||||||
|
<th scope="col" class="col-9">Note</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (Note note in Model)
|
||||||
|
{
|
||||||
|
<tr class="d-flex" style="cursor:pointer;" onclick="showEditNoteModal(@note.Id)">
|
||||||
|
<td class="col-3">@note.Description</td>
|
||||||
|
<td class="col-9 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(note.NoteText, 100)</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="modal fade" data-bs-focus="false" id="noteModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content" id="noteModalContent">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
<td class="col-2">@reminderRecord.Metric</td>
|
<td class="col-2">@reminderRecord.Metric</td>
|
||||||
}
|
}
|
||||||
<td class="col-5">@reminderRecord.Description</td>
|
<td class="col-5">@reminderRecord.Description</td>
|
||||||
<td class="col-3 text-truncate">@reminderRecord.Notes</td>
|
<td class="col-3 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(reminderRecord.Notes)</td>
|
||||||
<td class="col-1 text-truncate">
|
<td class="col-1 text-truncate">
|
||||||
<button type="button" class="btn btn-danger" onclick="deleteReminderRecord(@reminderRecord.Id, this)"><i class="bi bi-trash"></i></button>
|
<button type="button" class="btn btn-danger" onclick="deleteReminderRecord(@reminderRecord.Id, this)"><i class="bi bi-trash"></i></button>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<label for="serviceRecordDescription">Description</label>
|
<label for="serviceRecordDescription">Description</label>
|
||||||
<input type="text" id="serviceRecordDescription" class="form-control" placeholder="Description of item(s) serviced(i.e. Oil Change)" value="@Model.Description">
|
<input type="text" id="serviceRecordDescription" class="form-control" placeholder="Description of item(s) serviced(i.e. Oil Change)" value="@Model.Description">
|
||||||
<label for="serviceRecordCost">Cost</label>
|
<label for="serviceRecordCost">Cost</label>
|
||||||
<input type="number" id="serviceRecordCost" class="form-control" placeholder="Cost of the service" value="@(isNew ? "" : Model.Cost)">
|
<input type="text" id="serviceRecordCost" class="form-control" placeholder="Cost of the service" value="@(isNew ? "" : Model.Cost)">
|
||||||
</div>
|
</div>
|
||||||
<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>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('ServiceRecord')">Import via CSV</a></li>
|
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('ServiceRecord')">Import via CSV</a></li>
|
||||||
|
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('ServiceRecord')">Export to CSV</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -50,7 +51,7 @@
|
|||||||
<td class="col-2">@serviceRecord.Mileage</td>
|
<td class="col-2">@serviceRecord.Mileage</td>
|
||||||
<td class="col-4">@serviceRecord.Description</td>
|
<td class="col-4">@serviceRecord.Description</td>
|
||||||
<td class="col-2">@((hideZero && serviceRecord.Cost == default) ? "---" : serviceRecord.Cost.ToString("C"))</td>
|
<td class="col-2">@((hideZero && serviceRecord.Cost == default) ? "---" : serviceRecord.Cost.ToString("C"))</td>
|
||||||
<td class="col-3 text-truncate">@serviceRecord.Notes</td>
|
<td class="col-3 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(serviceRecord.Notes)</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<label for="taxRecordDescription">Description</label>
|
<label for="taxRecordDescription">Description</label>
|
||||||
<input type="text" id="taxRecordDescription" class="form-control" placeholder="Description of tax paid(i.e. Registration)" value="@Model.Description">
|
<input type="text" id="taxRecordDescription" class="form-control" placeholder="Description of tax paid(i.e. Registration)" value="@Model.Description">
|
||||||
<label for="taxRecordCost">Cost</label>
|
<label for="taxRecordCost">Cost</label>
|
||||||
<input type="number" id="taxRecordCost" class="form-control" placeholder="Cost of tax paid" value="@(isNew? "" : Model.Cost)">
|
<input type="text" id="taxRecordCost" class="form-control" placeholder="Cost of tax paid" value="@(isNew? "" : Model.Cost)">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-12">
|
<div class="col-md-6 col-12">
|
||||||
<label for="taxRecordNotes">Notes(optional)</label>
|
<label for="taxRecordNotes">Notes(optional)</label>
|
||||||
|
|||||||
@@ -19,7 +19,8 @@
|
|||||||
<span class="visually-hidden">Toggle Dropdown</span>
|
<span class="visually-hidden">Toggle Dropdown</span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('taxrecord')">Import via CSV</a></li>
|
<li><a class="dropdown-item" href="#" onclick="showBulkImportModal('TaxRecord')">Import via CSV</a></li>
|
||||||
|
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('TaxRecord')">Export to CSV</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -48,7 +49,7 @@
|
|||||||
<td class="col-1">@taxRecord.Date.ToShortDateString()</td>
|
<td class="col-1">@taxRecord.Date.ToShortDateString()</td>
|
||||||
<td class="col-6">@taxRecord.Description</td>
|
<td class="col-6">@taxRecord.Description</td>
|
||||||
<td class="col-2">@((hideZero && taxRecord.Cost == default) ? "---" : taxRecord.Cost.ToString("C"))</td>
|
<td class="col-2">@((hideZero && taxRecord.Cost == default) ? "---" : taxRecord.Cost.ToString("C"))</td>
|
||||||
<td class="col-3 text-truncate">@taxRecord.Notes</td>
|
<td class="col-3 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(taxRecord.Notes)</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
84
Views/Vehicle/_UpgradeRecordModal.cshtml
Normal file
84
Views/Vehicle/_UpgradeRecordModal.cshtml
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
@model UpgradeRecordInput
|
||||||
|
@{
|
||||||
|
var isNew = Model.Id == 0;
|
||||||
|
}
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">@(isNew ? "Add New Upgrade Record" : "Edit Upgrade Record")</h5>
|
||||||
|
<button type="button" class="btn-close" onclick="hideAddUpgradeRecordModal()" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
|
||||||
|
<label for="upgradeRecordDate">Date</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" id="upgradeRecordDate" class="form-control" placeholder="Date upgrade/mods was installed" value="@Model.Date">
|
||||||
|
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
|
||||||
|
</div>
|
||||||
|
<label for="upgradeRecordMileage">Odometer</label>
|
||||||
|
<input type="number" id="upgradeRecordMileage" class="form-control" placeholder="Odometer reading when upgraded/modded" value="@(isNew ? "" : Model.Mileage)">
|
||||||
|
<label for="upgradeRecordDescription">Description</label>
|
||||||
|
<input type="text" id="upgradeRecordDescription" class="form-control" placeholder="Description of item(s) upgraded/modded" value="@Model.Description">
|
||||||
|
<label for="upgradeRecordCost">Cost</label>
|
||||||
|
<input type="text" id="upgradeRecordCost" class="form-control" placeholder="Cost of the upgrade/mods" value="@(isNew ? "" : Model.Cost)">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 col-12">
|
||||||
|
<label for="upgradeRecordNotes">Notes(optional)</label>
|
||||||
|
<textarea id="upgradeRecordNotes" class="form-control" rows="5">@Model.Notes</textarea>
|
||||||
|
@if (Model.Files.Any())
|
||||||
|
{
|
||||||
|
<div>
|
||||||
|
@await Html.PartialAsync("_UploadedFiles", Model.Files)
|
||||||
|
<label for="upgradeRecordFiles">Upload more documents</label>
|
||||||
|
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="upgradeRecordFiles">
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@if (isNew)
|
||||||
|
{
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" value="" id="addReminderCheck">
|
||||||
|
<label class="form-check-label" for="addReminderCheck">
|
||||||
|
Add Reminder
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<label for="upgradeRecordFiles">Upload documents(optional)</label>
|
||||||
|
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="upgradeRecordFiles">
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
@if (!isNew)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-danger" onclick="deleteUpgradeRecord(@Model.Id)" style="margin-right:auto;">Delete</button>
|
||||||
|
}
|
||||||
|
<button type="button" class="btn btn-secondary" onclick="hideAddUpgradeRecordModal()">Cancel</button>
|
||||||
|
@if (isNew)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-primary" onclick="saveUpgradeRecordToVehicle()">Add New Upgrade Record</button>
|
||||||
|
}
|
||||||
|
else if (!isNew)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-primary" onclick="saveUpgradeRecordToVehicle(true)">Edit Upgrade Record</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
var uploadedFiles = [];
|
||||||
|
getUploadedFilesFromModel();
|
||||||
|
function getUploadedFilesFromModel() {
|
||||||
|
@foreach (UploadedFiles filesUploaded in Model.Files)
|
||||||
|
{
|
||||||
|
@:uploadedFiles.push({ name: "@filesUploaded.Name", location: "@filesUploaded.Location" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getUpgradeRecordModelData() {
|
||||||
|
return { id: @Model.Id}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
67
Views/Vehicle/_UpgradeRecords.cshtml
Normal file
67
Views/Vehicle/_UpgradeRecords.cshtml
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
@inject IConfiguration Configuration
|
||||||
|
@{
|
||||||
|
var enableCsvImports = bool.Parse(Configuration[nameof(UserConfig.EnableCsvImports)]);
|
||||||
|
var hideZero = bool.Parse(Configuration[nameof(UserConfig.HideZero)]);
|
||||||
|
}
|
||||||
|
@model List<UpgradeRecord>
|
||||||
|
<div class="row">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<div class="d-flex align-items-center flex-wrap">
|
||||||
|
<span class="ms-2 badge bg-success">@($"# of Upgrade Records: {Model.Count()}")</span>
|
||||||
|
<span class="ms-2 badge bg-primary">@($"Total: {Model.Sum(x => x.Cost).ToString("C")}")</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if (enableCsvImports)
|
||||||
|
{
|
||||||
|
<div class="btn-group">
|
||||||
|
<button onclick="showAddUpgradeRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Upgrade 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('UpgradeRecord')">Import via CSV</a></li>
|
||||||
|
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('UpgradeRecord')">Export to CSV</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<button onclick="showAddUpgradeRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Upgrade Record</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row vehicleDetailTabContainer">
|
||||||
|
<div class="col-12">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr class="d-flex">
|
||||||
|
<th scope="col" class="col-1">Date</th>
|
||||||
|
<th scope="col" class="col-2">Odometer</th>
|
||||||
|
<th scope="col" class="col-4">Description</th>
|
||||||
|
<th scope="col" class="col-2">Cost</th>
|
||||||
|
<th scope="col" class="col-3">Notes</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (UpgradeRecord upgradeRecord in Model)
|
||||||
|
{
|
||||||
|
<tr class="d-flex" style="cursor:pointer;" onclick="showEditUpgradeRecordModal(@upgradeRecord.Id)">
|
||||||
|
<td class="col-1">@upgradeRecord.Date.ToShortDateString()</td>
|
||||||
|
<td class="col-2">@upgradeRecord.Mileage</td>
|
||||||
|
<td class="col-4">@upgradeRecord.Description</td>
|
||||||
|
<td class="col-2">@((hideZero && upgradeRecord.Cost == default) ? "---" : upgradeRecord.Cost.ToString("C"))</td>
|
||||||
|
<td class="col-3 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(upgradeRecord.Notes)</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal fade" data-bs-focus="false" id="upgradeRecordModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
|
<div class="modal-content" id="upgradeRecordModalContent">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
BIN
docs/fuelmileage.png
Normal file
BIN
docs/fuelmileage.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
BIN
docs/garage.png
Normal file
BIN
docs/garage.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 109 KiB |
167
docs/index.html
Normal file
167
docs/index.html
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
<html lang="en" data-bs-theme="dark">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>LubeLogger</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
|
||||||
|
<style>
|
||||||
|
.customCarouselCaption{
|
||||||
|
background-image: linear-gradient(to bottom, rgba(255,0,0,0), #000);
|
||||||
|
padding-top: 3rem;
|
||||||
|
color: #fff !important
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container" style="height:85vh;">
|
||||||
|
<main role="main">
|
||||||
|
<div class="row mt-2">
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<img src="lubelogger_logo.png"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 d-flex justify-content-center">
|
||||||
|
<h6 class="display-6 text-center">Self-Hosted, Open-Source, Unconventionally-Named Vehicle Maintenance Records and Fuel Mileage Tracker</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 d-flex justify-content-center">
|
||||||
|
<h6 class="display-6 text-center">Showcase</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div id="carouselGallery" class="carousel slide">
|
||||||
|
<div class="carousel-indicators">
|
||||||
|
<button type="button" data-bs-target="#carouselGallery" data-bs-slide-to="0" class="active" aria-current="true"></button>
|
||||||
|
<button type="button" data-bs-target="#carouselGallery" data-bs-slide-to="1"></button>
|
||||||
|
<button type="button" data-bs-target="#carouselGallery" data-bs-slide-to="2"></button>
|
||||||
|
<button type="button" data-bs-target="#carouselGallery" data-bs-slide-to="3"></button>
|
||||||
|
</div>
|
||||||
|
<div class="carousel-inner">
|
||||||
|
<div class="carousel-item active">
|
||||||
|
<img src="garage.png" class="d-block w-100" alt="Gallery View">
|
||||||
|
<div class="carousel-caption d-none d-md-block customCarouselCaption">
|
||||||
|
<h5>Garage</h5>
|
||||||
|
<p>All of your vehicles conveniently displayed in one place</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="carousel-item">
|
||||||
|
<img src="servicerecord.png" class="d-block w-100" alt="...">
|
||||||
|
<div class="carousel-caption d-none d-md-block customCarouselCaption">
|
||||||
|
<h5>Service, Repair, and Upgrade Records</h5>
|
||||||
|
<p>Track all of the work done on your vehicles</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="carousel-item">
|
||||||
|
<img src="fuelmileage.png" class="d-block w-100" alt="...">
|
||||||
|
<div class="carousel-caption d-none d-md-block customCarouselCaption">
|
||||||
|
<h5>Fuel Mileage</h5>
|
||||||
|
<p>Keeps track of the fuel economy of your vehicles</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="carousel-item">
|
||||||
|
<img src="reminder.png" class="d-block w-100" alt="...">
|
||||||
|
<div class="carousel-caption d-none d-md-block customCarouselCaption">
|
||||||
|
<h5>Reminders</h5>
|
||||||
|
<p>Set up reminders so you never miss another scheduled maintenance</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="carousel-control-prev" type="button" data-bs-target="#carouselGallery" data-bs-slide="prev">
|
||||||
|
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
|
||||||
|
<span class="visually-hidden">Previous</span>
|
||||||
|
</button>
|
||||||
|
<button class="carousel-control-next" type="button" data-bs-target="#carouselGallery" data-bs-slide="next">
|
||||||
|
<span class="carousel-control-next-icon" aria-hidden="true"></span>
|
||||||
|
<span class="visually-hidden">Next</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 d-flex justify-content-center">
|
||||||
|
<h6 class="display-6 text-center">Features</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-md-6">
|
||||||
|
<ul class="list-group">
|
||||||
|
<li class="list-group-item">Keeps track of all your maintenance, repair, and upgrade records</li>
|
||||||
|
<li class="list-group-item">Keeps track of your fuel economy(supports MPG, UK MPG, and L/100KM)</li>
|
||||||
|
<li class="list-group-item">Keeps track of taxes(registration, going fast tax, etc)</li>
|
||||||
|
<li class="list-group-item">No limit on how many vehicles you have in your garage</li>
|
||||||
|
<li class="list-group-item">Import existing records from CSV(supports imports from Fuelly for Fuel Records)</li>
|
||||||
|
<li class="list-group-item">Attach documents for each record(receipts, invoices, etc)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-6">
|
||||||
|
<ul class="list-group">
|
||||||
|
<li class="list-group-item">Set reminders so you never miss another scheduled maintenance</li>
|
||||||
|
<li class="list-group-item">Dark Mode</li>
|
||||||
|
<li class="list-group-item">Mobile/Small screen support</li>
|
||||||
|
<li class="list-group-item">Basic Authentication for security</li>
|
||||||
|
<li class="list-group-item">Coming Soon(API Endpoints)</li>
|
||||||
|
<li class="list-group-item">Coming Soon(Consolidated Report Export - Just like CarFax)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 d-flex justify-content-center">
|
||||||
|
<h6 class="display-6 text-center">Where to Download</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 d-flex justify-content-center">
|
||||||
|
<p class="lead">
|
||||||
|
LubeLogger is released exclusively as a Docker Image via the GitHub Container Repository(GHCR)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 d-flex justify-content-center">
|
||||||
|
<p class="lead">
|
||||||
|
To pull down the Docker Image, run
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 d-flex justify-content-center">
|
||||||
|
<p><code>docker pull ghcr.io/hargata/lubelogger:latest</code></p>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 d-flex justify-content-center">
|
||||||
|
<p class="lead">
|
||||||
|
Check the <a href="https://github.com/hargata/lubelog/" target="_blank">GitHub repository</a> and clone the "docker-compose.yml" and ".env" file onto your computer.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 d-flex justify-content-center">
|
||||||
|
<p class="lead">
|
||||||
|
Navigate to the directory where those two files are located, then run
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 d-flex justify-content-center">
|
||||||
|
<p><code>docker-compose up</code></p>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 d-flex justify-content-center">
|
||||||
|
<p class="lead">
|
||||||
|
Navigate to the URL the service is listening on, the default is
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 d-flex justify-content-center">
|
||||||
|
<p><code>http://localhost:8080</code></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 d-flex justify-content-center">
|
||||||
|
<p class="lead">LubeLogger is proudly developed in Price Utah by Hargata Softworks
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 d-flex justify-content-center">
|
||||||
|
<p class="lead">For more information regarding Price Utah, click <a href="https://www.visitutah.com/Places-To-Go/Cities-and-Towns/Price" target="_blank">here</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
docs/lubelogger_logo.png
Normal file
BIN
docs/lubelogger_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.1 KiB |
BIN
docs/reminder.png
Normal file
BIN
docs/reminder.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
BIN
docs/servicerecord.png
Normal file
BIN
docs/servicerecord.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
@@ -103,7 +103,7 @@ function getAndValidateCollisionRecordValues() {
|
|||||||
} else {
|
} else {
|
||||||
$("#collisionRecordDescription").removeClass("is-invalid");
|
$("#collisionRecordDescription").removeClass("is-invalid");
|
||||||
}
|
}
|
||||||
if (collisionCost.trim() == '') {
|
if (collisionCost.trim() == '' || !isValidMoney(collisionCost)) {
|
||||||
hasError = true;
|
hasError = true;
|
||||||
$("#collisionRecordCost").addClass("is-invalid");
|
$("#collisionRecordCost").addClass("is-invalid");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ function getAndValidateGasRecordValues() {
|
|||||||
var gasGallons = $("#gasRecordGallons").val();
|
var gasGallons = $("#gasRecordGallons").val();
|
||||||
var gasCost = $("#gasRecordCost").val();
|
var gasCost = $("#gasRecordCost").val();
|
||||||
var gasIsFillToFull = $("#gasIsFillToFull").is(":checked");
|
var gasIsFillToFull = $("#gasIsFillToFull").is(":checked");
|
||||||
|
var gasIsMissed = $("#gasIsMissed").is(":checked");
|
||||||
var vehicleId = GetVehicleId().vehicleId;
|
var vehicleId = GetVehicleId().vehicleId;
|
||||||
var gasRecordId = getGasRecordModelData().id;
|
var gasRecordId = getGasRecordModelData().id;
|
||||||
//validation
|
//validation
|
||||||
@@ -99,7 +100,7 @@ function getAndValidateGasRecordValues() {
|
|||||||
} else {
|
} else {
|
||||||
$("#gasRecordGallons").removeClass("is-invalid");
|
$("#gasRecordGallons").removeClass("is-invalid");
|
||||||
}
|
}
|
||||||
if (gasCost.trim() == '') {
|
if (gasCost.trim() == '' || !isValidMoney(gasCost)) {
|
||||||
hasError = true;
|
hasError = true;
|
||||||
$("#gasRecordCost").addClass("is-invalid");
|
$("#gasRecordCost").addClass("is-invalid");
|
||||||
} else {
|
} else {
|
||||||
@@ -114,6 +115,7 @@ function getAndValidateGasRecordValues() {
|
|||||||
gallons: gasGallons,
|
gallons: gasGallons,
|
||||||
cost: gasCost,
|
cost: gasCost,
|
||||||
files: uploadedFiles,
|
files: uploadedFiles,
|
||||||
isFillToFull: gasIsFillToFull
|
isFillToFull: gasIsFillToFull,
|
||||||
|
missedFuelUp: gasIsMissed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
90
wwwroot/js/note.js
Normal file
90
wwwroot/js/note.js
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
function showAddNoteModal() {
|
||||||
|
$.get('/Vehicle/GetAddNotePartialView', function (data) {
|
||||||
|
if (data) {
|
||||||
|
$("#noteModalContent").html(data);
|
||||||
|
$('#noteModal').modal('show');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function showEditNoteModal(noteId) {
|
||||||
|
$.get(`/Vehicle/GetNoteForEditById?noteId=${noteId}`, function (data) {
|
||||||
|
if (data) {
|
||||||
|
$("#noteModalContent").html(data);
|
||||||
|
$('#noteModal').modal('show');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function hideAddNoteModal() {
|
||||||
|
$('#noteModal').modal('hide');
|
||||||
|
}
|
||||||
|
function deleteNote(noteId) {
|
||||||
|
$("#workAroundInput").show();
|
||||||
|
Swal.fire({
|
||||||
|
title: "Confirm Deletion?",
|
||||||
|
text: "Deleted Notes cannot be restored.",
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonText: "Delete",
|
||||||
|
confirmButtonColor: "#dc3545"
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
$.post(`/Vehicle/DeleteNoteById?noteId=${noteId}`, function (data) {
|
||||||
|
if (data) {
|
||||||
|
hideAddNoteModal();
|
||||||
|
successToast("Note Deleted");
|
||||||
|
var vehicleId = GetVehicleId().vehicleId;
|
||||||
|
getVehicleNotes(vehicleId);
|
||||||
|
} else {
|
||||||
|
errorToast("An error has occurred, please try again later.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$("#workAroundInput").hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function saveNoteToVehicle(isEdit) {
|
||||||
|
//get values
|
||||||
|
var formValues = getAndValidateNoteValues();
|
||||||
|
//validate
|
||||||
|
if (formValues.hasError) {
|
||||||
|
errorToast("Please check the form data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//save to db.
|
||||||
|
$.post('/Vehicle/SaveNoteToVehicleId', { note: formValues }, function (data) {
|
||||||
|
if (data) {
|
||||||
|
successToast(isEdit ? "Note Updated" : "Note Added.");
|
||||||
|
hideAddNoteModal();
|
||||||
|
getVehicleNotes(formValues.vehicleId);
|
||||||
|
} else {
|
||||||
|
errorToast("An error has occurred, please try again later.");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function getAndValidateNoteValues() {
|
||||||
|
var noteDescription = $("#noteDescription").val();
|
||||||
|
var noteText = $("#noteTextArea").val();
|
||||||
|
var vehicleId = GetVehicleId().vehicleId;
|
||||||
|
var noteId = getNoteModelData().id;
|
||||||
|
//validation
|
||||||
|
var hasError = false;
|
||||||
|
if (noteDescription.trim() == '') { //eliminates whitespace.
|
||||||
|
hasError = true;
|
||||||
|
$("#noteDescription").addClass("is-invalid");
|
||||||
|
} else {
|
||||||
|
$("#noteDescription").removeClass("is-invalid");
|
||||||
|
}
|
||||||
|
if (noteText.trim() == '') {
|
||||||
|
hasError = true;
|
||||||
|
$("#noteTextArea").addClass("is-invalid");
|
||||||
|
} else {
|
||||||
|
$("#noteTextArea").removeClass("is-invalid");
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: noteId,
|
||||||
|
hasError: hasError,
|
||||||
|
vehicleId: vehicleId,
|
||||||
|
description: noteDescription,
|
||||||
|
noteText: noteText
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -103,7 +103,7 @@ function getAndValidateServiceRecordValues() {
|
|||||||
} else {
|
} else {
|
||||||
$("#serviceRecordDescription").removeClass("is-invalid");
|
$("#serviceRecordDescription").removeClass("is-invalid");
|
||||||
}
|
}
|
||||||
if (serviceCost.trim() == '') {
|
if (serviceCost.trim() == '' || !isValidMoney(serviceCost)) {
|
||||||
hasError = true;
|
hasError = true;
|
||||||
$("#serviceRecordCost").addClass("is-invalid");
|
$("#serviceRecordCost").addClass("is-invalid");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -110,4 +110,9 @@ function uploadFileAsync(event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
function isValidMoney(input) {
|
||||||
|
const euRegex = /^\$?(?=\(.*\)|[^()]*$)\(?\d{1,3}(\.?\d{3})?(,\d{1,3}?)?\)?$/;
|
||||||
|
const usRegex = /^\$?(?=\(.*\)|[^()]*$)\(?\d{1,3}(,?\d{3})?(\.\d{1,3}?)?\)?$/;
|
||||||
|
return (euRegex.test(input) || usRegex.test(input));
|
||||||
}
|
}
|
||||||
@@ -96,7 +96,7 @@ function getAndValidateTaxRecordValues() {
|
|||||||
} else {
|
} else {
|
||||||
$("#taxRecordDescription").removeClass("is-invalid");
|
$("#taxRecordDescription").removeClass("is-invalid");
|
||||||
}
|
}
|
||||||
if (taxCost.trim() == '') {
|
if (taxCost.trim() == '' || !isValidMoney(taxCost)) {
|
||||||
hasError = true;
|
hasError = true;
|
||||||
$("#taxRecordCost").addClass("is-invalid");
|
$("#taxRecordCost").addClass("is-invalid");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
124
wwwroot/js/upgraderecord.js
Normal file
124
wwwroot/js/upgraderecord.js
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
function showAddUpgradeRecordModal() {
|
||||||
|
$.get('/Vehicle/GetAddUpgradeRecordPartialView', function (data) {
|
||||||
|
if (data) {
|
||||||
|
$("#upgradeRecordModalContent").html(data);
|
||||||
|
//initiate datepicker
|
||||||
|
$('#upgradeRecordDate').datepicker({
|
||||||
|
endDate: "+0d",
|
||||||
|
format: getShortDatePattern().pattern
|
||||||
|
});
|
||||||
|
$('#upgradeRecordModal').modal('show');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function showEditUpgradeRecordModal(upgradeRecordId) {
|
||||||
|
$.get(`/Vehicle/GetUpgradeRecordForEditById?upgradeRecordId=${upgradeRecordId}`, function (data) {
|
||||||
|
if (data) {
|
||||||
|
$("#upgradeRecordModalContent").html(data);
|
||||||
|
//initiate datepicker
|
||||||
|
$('#upgradeRecordDate').datepicker({
|
||||||
|
endDate: "+0d",
|
||||||
|
format: getShortDatePattern().pattern
|
||||||
|
});
|
||||||
|
$('#upgradeRecordModal').modal('show');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function hideAddUpgradeRecordModal() {
|
||||||
|
$('#upgradeRecordModal').modal('hide');
|
||||||
|
}
|
||||||
|
function deleteUpgradeRecord(upgradeRecordId) {
|
||||||
|
$("#workAroundInput").show();
|
||||||
|
Swal.fire({
|
||||||
|
title: "Confirm Deletion?",
|
||||||
|
text: "Deleted Upgrade Records cannot be restored.",
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonText: "Delete",
|
||||||
|
confirmButtonColor: "#dc3545"
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
$.post(`/Vehicle/DeleteUpgradeRecordById?upgradeRecordId=${upgradeRecordId}`, function (data) {
|
||||||
|
if (data) {
|
||||||
|
hideAddUpgradeRecordModal();
|
||||||
|
successToast("Upgrade Record Deleted");
|
||||||
|
var vehicleId = GetVehicleId().vehicleId;
|
||||||
|
getVehicleUpgradeRecords(vehicleId);
|
||||||
|
} else {
|
||||||
|
errorToast("An error has occurred, please try again later.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$("#workAroundInput").hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function saveUpgradeRecordToVehicle(isEdit) {
|
||||||
|
//get values
|
||||||
|
var formValues = getAndValidateUpgradeRecordValues();
|
||||||
|
//validate
|
||||||
|
if (formValues.hasError) {
|
||||||
|
errorToast("Please check the form data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//save to db.
|
||||||
|
$.post('/Vehicle/SaveUpgradeRecordToVehicleId', { upgradeRecord: formValues }, function (data) {
|
||||||
|
if (data) {
|
||||||
|
successToast(isEdit ? "Upgrade Record Updated" : "Upgrade Record Added.");
|
||||||
|
hideAddUpgradeRecordModal();
|
||||||
|
getVehicleUpgradeRecords(formValues.vehicleId);
|
||||||
|
if (formValues.addReminderRecord) {
|
||||||
|
setTimeout(function () { showAddReminderModal(formValues); }, 500);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errorToast("An error has occurred, please try again later.");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function getAndValidateUpgradeRecordValues() {
|
||||||
|
var upgradeDate = $("#upgradeRecordDate").val();
|
||||||
|
var upgradeMileage = $("#upgradeRecordMileage").val();
|
||||||
|
var upgradeDescription = $("#upgradeRecordDescription").val();
|
||||||
|
var upgradeCost = $("#upgradeRecordCost").val();
|
||||||
|
var upgradeNotes = $("#upgradeRecordNotes").val();
|
||||||
|
var vehicleId = GetVehicleId().vehicleId;
|
||||||
|
var upgradeRecordId = getUpgradeRecordModelData().id;
|
||||||
|
var addReminderRecord = $("#addReminderCheck").is(":checked");
|
||||||
|
//validation
|
||||||
|
var hasError = false;
|
||||||
|
if (upgradeDate.trim() == '') { //eliminates whitespace.
|
||||||
|
hasError = true;
|
||||||
|
$("#upgradeRecordDate").addClass("is-invalid");
|
||||||
|
} else {
|
||||||
|
$("#upgradeRecordDate").removeClass("is-invalid");
|
||||||
|
}
|
||||||
|
if (upgradeMileage.trim() == '' || parseInt(upgradeMileage) < 0) {
|
||||||
|
hasError = true;
|
||||||
|
$("#upgradeRecordMileage").addClass("is-invalid");
|
||||||
|
} else {
|
||||||
|
$("#upgradeRecordMileage").removeClass("is-invalid");
|
||||||
|
}
|
||||||
|
if (upgradeDescription.trim() == '') {
|
||||||
|
hasError = true;
|
||||||
|
$("#upgradeRecordDescription").addClass("is-invalid");
|
||||||
|
} else {
|
||||||
|
$("#upgradeRecordDescription").removeClass("is-invalid");
|
||||||
|
}
|
||||||
|
if (upgradeCost.trim() == '' || !isValidMoney(upgradeCost)) {
|
||||||
|
hasError = true;
|
||||||
|
$("#upgradeRecordCost").addClass("is-invalid");
|
||||||
|
} else {
|
||||||
|
$("#upgradeRecordCost").removeClass("is-invalid");
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: upgradeRecordId,
|
||||||
|
hasError: hasError,
|
||||||
|
vehicleId: vehicleId,
|
||||||
|
date: upgradeDate,
|
||||||
|
mileage: upgradeMileage,
|
||||||
|
description: upgradeDescription,
|
||||||
|
cost: upgradeCost,
|
||||||
|
notes: upgradeNotes,
|
||||||
|
files: uploadedFiles,
|
||||||
|
addReminderRecord: addReminderRecord
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,6 @@
|
|||||||
function returnToGarage() {
|
function returnToGarage() {
|
||||||
window.location.href = '/Home';
|
window.location.href = '/Home';
|
||||||
}
|
}
|
||||||
function saveVehicleNote(vehicleId) {
|
|
||||||
var noteText = $("#noteTextArea").val();
|
|
||||||
$.post('/Vehicle/SaveNoteToVehicle', { vehicleId: vehicleId, noteText: noteText }, function (data) {
|
|
||||||
if (data) {
|
|
||||||
successToast("Note saved successfully.");
|
|
||||||
} else {
|
|
||||||
errorToast("An error has occurred, please try again later.");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
var vehicleId = GetVehicleId().vehicleId;
|
var vehicleId = GetVehicleId().vehicleId;
|
||||||
//bind tabs
|
//bind tabs
|
||||||
@@ -20,7 +10,7 @@ $(document).ready(function () {
|
|||||||
getVehicleServiceRecords(vehicleId);
|
getVehicleServiceRecords(vehicleId);
|
||||||
break;
|
break;
|
||||||
case "notes-tab":
|
case "notes-tab":
|
||||||
getVehicleNote(vehicleId);
|
getVehicleNotes(vehicleId);
|
||||||
break;
|
break;
|
||||||
case "gas-tab":
|
case "gas-tab":
|
||||||
getVehicleGasRecords(vehicleId);
|
getVehicleGasRecords(vehicleId);
|
||||||
@@ -37,6 +27,9 @@ $(document).ready(function () {
|
|||||||
case "reminder-tab":
|
case "reminder-tab":
|
||||||
getVehicleReminders(vehicleId);
|
getVehicleReminders(vehicleId);
|
||||||
break;
|
break;
|
||||||
|
case "upgrade-tab":
|
||||||
|
getVehicleUpgradeRecords(vehicleId);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
switch (e.relatedTarget.id) { //clear out previous tabs with grids in them to help with performance
|
switch (e.relatedTarget.id) { //clear out previous tabs with grids in them to help with performance
|
||||||
case "servicerecord-tab":
|
case "servicerecord-tab":
|
||||||
@@ -57,15 +50,21 @@ $(document).ready(function () {
|
|||||||
case "reminder-tab":
|
case "reminder-tab":
|
||||||
$("#reminder-tab-pane").html("");
|
$("#reminder-tab-pane").html("");
|
||||||
break;
|
break;
|
||||||
|
case "upgrade-tab":
|
||||||
|
$("#upgrade-tab-pane").html("");
|
||||||
|
break;
|
||||||
|
case "notes-tab":
|
||||||
|
$("#notes-tab-pane").html("");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
getVehicleServiceRecords(vehicleId);
|
getVehicleServiceRecords(vehicleId);
|
||||||
});
|
});
|
||||||
|
|
||||||
function getVehicleNote(vehicleId) {
|
function getVehicleNotes(vehicleId) {
|
||||||
$.get(`/Vehicle/GetNoteByVehicleId?vehicleId=${vehicleId}`, function (data) {
|
$.get(`/Vehicle/GetNotesByVehicleId?vehicleId=${vehicleId}`, function (data) {
|
||||||
if (data) {
|
if (data) {
|
||||||
$("#noteTextArea").val(data);
|
$("#notes-tab-pane").html(data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -75,7 +74,15 @@ function getVehicleServiceRecords(vehicleId) {
|
|||||||
$("#servicerecord-tab-pane").html(data);
|
$("#servicerecord-tab-pane").html(data);
|
||||||
getVehicleHaveImportantReminders(vehicleId);
|
getVehicleHaveImportantReminders(vehicleId);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
}
|
||||||
|
function getVehicleUpgradeRecords(vehicleId) {
|
||||||
|
$.get(`/Vehicle/GetUpgradeRecordsByVehicleId?vehicleId=${vehicleId}`, function (data) {
|
||||||
|
if (data) {
|
||||||
|
$("#upgrade-tab-pane").html(data);
|
||||||
|
getVehicleHaveImportantReminders(vehicleId);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
function getVehicleGasRecords(vehicleId) {
|
function getVehicleGasRecords(vehicleId) {
|
||||||
$.get(`/Vehicle/GetGasRecordsByVehicleId?vehicleId=${vehicleId}`, function (data) {
|
$.get(`/Vehicle/GetGasRecordsByVehicleId?vehicleId=${vehicleId}`, function (data) {
|
||||||
@@ -97,7 +104,6 @@ function getVehicleTaxRecords(vehicleId) {
|
|||||||
$.get(`/Vehicle/GetTaxRecordsByVehicleId?vehicleId=${vehicleId}`, function (data) {
|
$.get(`/Vehicle/GetTaxRecordsByVehicleId?vehicleId=${vehicleId}`, function (data) {
|
||||||
if (data) {
|
if (data) {
|
||||||
$("#tax-tab-pane").html(data);
|
$("#tax-tab-pane").html(data);
|
||||||
getVehicleHaveImportantReminders(vehicleId);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -127,6 +133,16 @@ function editVehicle(vehicleId) {
|
|||||||
function hideEditVehicleModal() {
|
function hideEditVehicleModal() {
|
||||||
$('#editVehicleModal').modal('hide');
|
$('#editVehicleModal').modal('hide');
|
||||||
}
|
}
|
||||||
|
function exportVehicleData(mode) {
|
||||||
|
var vehicleId = GetVehicleId().vehicleId;
|
||||||
|
$.get('/Vehicle/ExportFromVehicleToCsv', { vehicleId: vehicleId, mode: mode }, function (data) {
|
||||||
|
if (!data) {
|
||||||
|
errorToast("An error occurred, please try again later");
|
||||||
|
} else {
|
||||||
|
window.location.href = data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
function showBulkImportModal(mode) {
|
function showBulkImportModal(mode) {
|
||||||
$.get(`/Vehicle/GetBulkImportModalPartialView?mode=${mode}`, function (data) {
|
$.get(`/Vehicle/GetBulkImportModalPartialView?mode=${mode}`, function (data) {
|
||||||
if (data) {
|
if (data) {
|
||||||
|
|||||||
Reference in New Issue
Block a user