Merge pull request #113 from hargata/Hargata/supplies

Supplies Tab
This commit is contained in:
Hargata Softworks 2024-01-17 11:04:25 -07:00 committed by GitHub
commit e1a5a871ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 980 additions and 60 deletions

View File

@ -46,12 +46,25 @@ namespace CarCareTracker.Controllers
}
[HttpPost]
public ActionResult DeleteFiles(string fileLocation)
public IActionResult DeleteFiles(string fileLocation)
{
var result = _fileHelper.DeleteFile(fileLocation);
return Json(result);
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpGet]
public IActionResult MakeBackup()
{
var result = _fileHelper.MakeBackup();
return Json(result);
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpPost]
public IActionResult RestoreBackup(string fileName)
{
var result = _fileHelper.RestoreBackup(fileName);
return Json(result);
}
private string UploadFile(IFormFile fileToUpload)
{
string uploadDirectory = "temp/";

View File

@ -25,6 +25,7 @@ namespace CarCareTracker.Controllers
private readonly ITaxRecordDataAccess _taxRecordDataAccess;
private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
private readonly IUpgradeRecordDataAccess _upgradeRecordDataAccess;
private readonly ISupplyRecordDataAccess _supplyRecordDataAccess;
private readonly IWebHostEnvironment _webEnv;
private readonly bool _useDescending;
private readonly IConfigHelper _config;
@ -47,6 +48,7 @@ namespace CarCareTracker.Controllers
ITaxRecordDataAccess taxRecordDataAccess,
IReminderRecordDataAccess reminderRecordDataAccess,
IUpgradeRecordDataAccess upgradeRecordDataAccess,
ISupplyRecordDataAccess supplyRecordDataAccess,
IUserLogic userLogic,
IWebHostEnvironment webEnv,
IConfigHelper config)
@ -64,6 +66,7 @@ namespace CarCareTracker.Controllers
_taxRecordDataAccess = taxRecordDataAccess;
_reminderRecordDataAccess = reminderRecordDataAccess;
_upgradeRecordDataAccess = upgradeRecordDataAccess;
_supplyRecordDataAccess = supplyRecordDataAccess;
_userLogic = userLogic;
_webEnv = webEnv;
_config = config;
@ -133,6 +136,7 @@ namespace CarCareTracker.Controllers
_noteDataAccess.DeleteAllNotesByVehicleId(vehicleId) &&
_reminderRecordDataAccess.DeleteAllReminderRecordsByVehicleId(vehicleId) &&
_upgradeRecordDataAccess.DeleteAllUpgradeRecordsByVehicleId(vehicleId) &&
_supplyRecordDataAccess.DeleteAllSupplyRecordsByVehicleId(vehicleId) &&
_userLogic.DeleteAllAccessToVehicle(vehicleId) &&
_dataAccess.DeleteVehicle(vehicleId);
return Json(result);
@ -209,7 +213,33 @@ namespace CarCareTracker.Controllers
return Json($"/{fileNameToExport}");
}
}
else if (mode == ImportMode.TaxRecord) {
else if (mode == ImportMode.SupplyRecord)
{
var fileNameToExport = $"temp/{Guid.NewGuid()}.csv";
var fullExportFilePath = _fileHelper.GetFullFilePath(fileNameToExport, false);
var vehicleRecords = _supplyRecordDataAccess.GetSupplyRecordsByVehicleId(vehicleId);
if (vehicleRecords.Any())
{
var exportData = vehicleRecords.Select(x => new SupplyRecordExportModel {
Date = x.Date.ToShortDateString(),
Description = x.Description,
Cost = x.Cost.ToString("C"),
PartNumber = x.PartNumber,
PartQuantity = x.Quantity.ToString(),
PartSupplier = x.PartSupplier,
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.TaxRecord)
{
var fileNameToExport = $"temp/{Guid.NewGuid()}.csv";
var fullExportFilePath = _fileHelper.GetFullFilePath(fileNameToExport, false);
var vehicleRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
@ -235,11 +265,12 @@ namespace CarCareTracker.Controllers
bool useUKMPG = _config.GetUserConfig(User).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(),
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(),
IsFillToFull = x.IsFillToFull.ToString(),
MissedFuelUp = x.MissedFuelUp.ToString()
@ -278,7 +309,7 @@ namespace CarCareTracker.Controllers
config.PrepareHeaderForMatch = args => { return args.Header.Trim().ToLower(); };
using (var csv = new CsvReader(reader, config))
{
csv.Context.RegisterClassMap<FuellyMapper>();
csv.Context.RegisterClassMap<ImportMapper>();
var records = csv.GetRecords<ImportModel>().ToList();
if (records.Any())
{
@ -300,7 +331,8 @@ namespace CarCareTracker.Controllers
//fuelly sometimes exports CSVs without total cost.
var parsedPrice = decimal.Parse(importModel.Price, NumberStyles.Any);
convertedRecord.Cost = convertedRecord.Gallons * parsedPrice;
} else
}
else
{
convertedRecord.Cost = decimal.Parse(importModel.Cost, NumberStyles.Any);
}
@ -308,7 +340,8 @@ namespace CarCareTracker.Controllers
{
var parsedBool = importModel.PartialFuelUp.Trim() == "1";
convertedRecord.IsFillToFull = !parsedBool;
} else if (!string.IsNullOrWhiteSpace(importModel.IsFillToFull))
}
else if (!string.IsNullOrWhiteSpace(importModel.IsFillToFull))
{
var possibleFillToFullValues = new List<string> { "1", "true", "full" };
var parsedBool = possibleFillToFullValues.Contains(importModel.IsFillToFull.Trim().ToLower());
@ -365,6 +398,21 @@ namespace CarCareTracker.Controllers
};
_upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(convertedRecord);
}
else if (mode == ImportMode.SupplyRecord)
{
var convertedRecord = new SupplyRecord()
{
VehicleId = vehicleId,
Date = DateTime.Parse(importModel.Date),
PartNumber = importModel.PartNumber,
PartSupplier = importModel.PartSupplier,
Quantity = decimal.Parse(importModel.PartQuantity, NumberStyles.Any),
Description = importModel.Description,
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
Notes = importModel.Notes
};
_supplyRecordDataAccess.SaveSupplyRecordToVehicle(convertedRecord);
}
else if (mode == ImportMode.TaxRecord)
{
var convertedRecord = new TaxRecord()
@ -678,9 +726,9 @@ namespace CarCareTracker.Controllers
{
numbersArray.Add(upgradeRecords.Min(x => x.Date.Year));
}
var minYear = numbersArray.Any() ? numbersArray.Min() : DateTime.Now.AddYears(-5).Year;
var minYear = numbersArray.Any() ? numbersArray.Min() : DateTime.Now.AddYears(-5).Year;
var yearDifference = DateTime.Now.Year - minYear + 1;
for(int i = 0; i < yearDifference; i++)
for (int i = 0; i < yearDifference; i++)
{
viewModel.Years.Add(DateTime.Now.AddYears(i * -1).Year);
}
@ -691,16 +739,16 @@ namespace CarCareTracker.Controllers
var userConfig = _config.GetUserConfig(User);
var mileageData = _gasHelper.GetGasRecordViewModels(gasRecords, userConfig.UseMPG, userConfig.UseUKMPG);
mileageData.RemoveAll(x => x.MilesPerGallon == default);
var monthlyMileageData = mileageData.GroupBy(x=>x.MonthId).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
var monthlyMileageData = mileageData.GroupBy(x => x.MonthId).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
{
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Average(y=>y.MilesPerGallon)
Cost = x.Average(y => y.MilesPerGallon)
}).ToList();
viewModel.FuelMileageForVehicleByMonth = monthlyMileageData;
return PartialView("_Report", viewModel);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
[HttpGet]
public IActionResult GetCollaboratorsForVehicle(int vehicleId)
{
var result = _userLogic.GetCollaboratorsForVehicle(vehicleId);
@ -743,7 +791,7 @@ namespace CarCareTracker.Controllers
GasRecordSum = gasRecords.Sum(x => x.Cost),
CollisionRecordSum = collisionRecords.Sum(x => x.Cost),
TaxRecordSum = taxRecords.Sum(x => x.Cost),
UpgradeRecordSum = upgradeRecords.Sum(x=>x.Cost)
UpgradeRecordSum = upgradeRecords.Sum(x => x.Cost)
};
return PartialView("_CostMakeUpReport", viewModel);
}
@ -821,7 +869,7 @@ namespace CarCareTracker.Controllers
Cost = x.Cost,
DataType = ImportMode.TaxRecord
}));
vehicleHistory.VehicleHistory = reportData.OrderBy(x=>x.Date).ThenBy(x=>x.Odometer).ToList();
vehicleHistory.VehicleHistory = reportData.OrderBy(x => x.Date).ThenBy(x => x.Odometer).ToList();
return PartialView("_VehicleHistory", vehicleHistory);
}
[TypeFilter(typeof(CollaboratorFilter))]
@ -1062,5 +1110,61 @@ namespace CarCareTracker.Controllers
return Json(result);
}
#endregion
#region "Supply Records"
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetSupplyRecordsByVehicleId(int vehicleId)
{
var result = _supplyRecordDataAccess.GetSupplyRecordsByVehicleId(vehicleId);
if (_useDescending)
{
result = result.OrderByDescending(x => x.Date).ToList();
}
else
{
result = result.OrderBy(x => x.Date).ToList();
}
return PartialView("_SupplyRecords", result);
}
[HttpPost]
public IActionResult SaveSupplyRecordToVehicleId(SupplyRecordInput supplyRecord)
{
//move files from temp.
supplyRecord.Files = supplyRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
var result = _supplyRecordDataAccess.SaveSupplyRecordToVehicle(supplyRecord.ToSupplyRecord());
return Json(result);
}
[HttpGet]
public IActionResult GetAddSupplyRecordPartialView()
{
return PartialView("_SupplyRecordModal", new SupplyRecordInput());
}
[HttpGet]
public IActionResult GetSupplyRecordForEditById(int supplyRecordId)
{
var result = _supplyRecordDataAccess.GetSupplyRecordById(supplyRecordId);
//convert to Input object.
var convertedResult = new SupplyRecordInput
{
Id = result.Id,
Cost = result.Cost,
Date = result.Date.ToShortDateString(),
Description = result.Description,
PartNumber = result.PartNumber,
Quantity = result.Quantity,
PartSupplier = result.PartSupplier,
Notes = result.Notes,
VehicleId = result.VehicleId,
Files = result.Files
};
return PartialView("_SupplyRecordModal", convertedResult);
}
[HttpPost]
public IActionResult DeleteSupplyRecordById(int supplyRecordId)
{
var result = _supplyRecordDataAccess.DeleteSupplyRecordById(supplyRecordId);
return Json(result);
}
#endregion
}
}

View File

@ -6,6 +6,10 @@
RepairRecord = 1,
GasRecord = 2,
TaxRecord = 3,
UpgradeRecord = 4
UpgradeRecord = 4,
ReminderRecord = 5,
NoteRecord = 6,
SupplyRecord = 7,
Dashboard = 8
}
}

View File

@ -0,0 +1,57 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using LiteDB;
namespace CarCareTracker.External.Implementations
{
public class SupplyRecordDataAccess : ISupplyRecordDataAccess
{
private static string dbName = StaticHelper.DbName;
private static string tableName = "supplyrecords";
public List<SupplyRecord> GetSupplyRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<SupplyRecord>(tableName);
var supplyRecords = table.Find(Query.EQ(nameof(SupplyRecord.VehicleId), vehicleId));
return supplyRecords.ToList() ?? new List<SupplyRecord>();
};
}
public SupplyRecord GetSupplyRecordById(int supplyRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<SupplyRecord>(tableName);
return table.FindById(supplyRecordId);
};
}
public bool DeleteSupplyRecordById(int supplyRecordId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<SupplyRecord>(tableName);
table.Delete(supplyRecordId);
return true;
};
}
public bool SaveSupplyRecordToVehicle(SupplyRecord supplyRecord)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<SupplyRecord>(tableName);
table.Upsert(supplyRecord);
return true;
};
}
public bool DeleteAllSupplyRecordsByVehicleId(int vehicleId)
{
using (var db = new LiteDatabase(dbName))
{
var table = db.GetCollection<SupplyRecord>(tableName);
var supplyRecords = table.DeleteMany(Query.EQ(nameof(SupplyRecord.VehicleId), vehicleId));
return true;
};
}
}
}

View File

@ -0,0 +1,13 @@
using CarCareTracker.Models;
namespace CarCareTracker.External.Interfaces
{
public interface ISupplyRecordDataAccess
{
public List<SupplyRecord> GetSupplyRecordsByVehicleId(int vehicleId);
public SupplyRecord GetSupplyRecordById(int supplyRecordId);
public bool DeleteSupplyRecordById(int supplyRecordId);
public bool SaveSupplyRecordToVehicle(SupplyRecord supplyRecord);
public bool DeleteAllSupplyRecordsByVehicleId(int vehicleId);
}
}

View File

@ -94,7 +94,9 @@ namespace CarCareTracker.Helper
EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)]),
HideZero = bool.Parse(_config[nameof(UserConfig.HideZero)]),
UseUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)]),
UseThreeDecimalGasCost = bool.Parse(_config[nameof(UserConfig.UseThreeDecimalGasCost)])
UseThreeDecimalGasCost = bool.Parse(_config[nameof(UserConfig.UseThreeDecimalGasCost)]),
VisibleTabs = _config.GetSection("VisibleTabs").Get<List<ImportMode>>(),
DefaultTab = (ImportMode)int.Parse(_config[nameof(UserConfig.DefaultTab)])
};
int userId = 0;
if (user != null)

View File

@ -1,12 +1,16 @@
namespace CarCareTracker.Helper
using System.IO.Compression;
namespace CarCareTracker.Helper
{
public interface IFileHelper
{
string GetFullFilePath(string currentFilePath, bool mustExist = true);
string MoveFileFromTemp(string currentFilePath, string newFolder);
bool DeleteFile(string currentFilePath);
string MakeBackup();
bool RestoreBackup(string fileName);
}
public class FileHelper: IFileHelper
public class FileHelper : IFileHelper
{
private readonly IWebHostEnvironment _webEnv;
public FileHelper(IWebHostEnvironment webEnv)
@ -23,7 +27,8 @@
if (File.Exists(oldFilePath))
{
return oldFilePath;
} else if (!mustExist)
}
else if (!mustExist)
{
return oldFilePath;
}
@ -31,6 +36,111 @@
return string.Empty;
}
}
public bool RestoreBackup(string fileName)
{
var fullFilePath = GetFullFilePath(fileName);
if (string.IsNullOrWhiteSpace(fullFilePath))
{
return false;
}
try
{
var tempPath = Path.Combine(_webEnv.WebRootPath, $"temp/{Guid.NewGuid()}");
if (!Directory.Exists(tempPath))
Directory.CreateDirectory(tempPath);
//extract zip file
ZipFile.ExtractToDirectory(fullFilePath, tempPath);
//copy over images and documents.
var imagePath = Path.Combine(tempPath, "images");
var documentPath = Path.Combine(tempPath, "documents");
var dataPath = Path.Combine(tempPath, StaticHelper.DbName);
var configPath = Path.Combine(tempPath, StaticHelper.UserConfigPath);
if (Directory.Exists(imagePath))
{
var existingPath = Path.Combine(_webEnv.WebRootPath, "images");
if (Directory.Exists(existingPath))
{
Directory.Delete(existingPath, true);
}
Directory.Move(imagePath, existingPath);
}
if (Directory.Exists(documentPath))
{
var existingPath = Path.Combine(_webEnv.WebRootPath, "documents");
if (Directory.Exists(existingPath))
{
Directory.Delete(existingPath, true);
}
Directory.Move(documentPath, existingPath);
}
if (File.Exists(dataPath))
{
//data path will always exist as it is created on startup if not.
File.Move(dataPath, StaticHelper.DbName, true);
}
if (File.Exists(configPath))
{
//check if config folder exists.
if (!Directory.Exists("config/"))
{
Directory.CreateDirectory("config/");
}
File.Move(configPath, StaticHelper.UserConfigPath, true);
}
return true;
} catch (Exception ex)
{
return false;
}
}
public string MakeBackup()
{
var folderName = $"db_backup_{DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss")}";
var tempPath = Path.Combine(_webEnv.WebRootPath, $"temp/{folderName}");
var imagePath = Path.Combine(_webEnv.WebRootPath, "images");
var documentPath = Path.Combine(_webEnv.WebRootPath, "documents");
var dataPath = StaticHelper.DbName;
var configPath = StaticHelper.UserConfigPath;
if (!Directory.Exists(tempPath))
Directory.CreateDirectory(tempPath);
if (Directory.Exists(imagePath))
{
var files = Directory.GetFiles(imagePath);
foreach (var file in files)
{
var newPath = Path.Combine(tempPath, "images");
Directory.CreateDirectory(newPath);
File.Copy(file, $"{newPath}/{Path.GetFileName(file)}");
}
}
if (Directory.Exists(documentPath))
{
var files = Directory.GetFiles(documentPath);
foreach (var file in files)
{
var newPath = Path.Combine(tempPath, "documents");
Directory.CreateDirectory(newPath);
File.Copy(file, $"{newPath}/{Path.GetFileName(file)}");
}
}
if (File.Exists(dataPath))
{
var newPath = Path.Combine(tempPath, "data");
Directory.CreateDirectory(newPath);
File.Copy(dataPath, $"{newPath}/{Path.GetFileName(dataPath)}");
}
if (File.Exists(configPath))
{
var newPath = Path.Combine(tempPath, "config");
Directory.CreateDirectory(newPath);
File.Copy(configPath, $"{newPath}/{Path.GetFileName(configPath)}");
}
var destFilePath = $"{tempPath}.zip";
ZipFile.CreateFromDirectory(tempPath, destFilePath);
//delete temp directory
Directory.Delete(tempPath, true);
return $"/temp/{folderName}.zip";
}
public string MoveFileFromTemp(string currentFilePath, string newFolder)
{
string tempPath = "temp/";
@ -38,7 +148,8 @@
{
return currentFilePath;
}
if (currentFilePath.StartsWith("/")) {
if (currentFilePath.StartsWith("/"))
{
currentFilePath = currentFilePath.Substring(1);
}
string uploadPath = Path.Combine(_webEnv.WebRootPath, newFolder);
@ -67,7 +178,8 @@
if (!File.Exists(filePath)) //verify file no longer exists.
{
return true;
} else
}
else
{
return false;
}

View File

@ -1,4 +1,6 @@
namespace CarCareTracker.Helper
using CarCareTracker.Models;
namespace CarCareTracker.Helper
{
/// <summary>
/// helper method for static vars
@ -18,10 +20,48 @@
if (input.Length > maxLength)
{
return (input.Substring(0, maxLength) + "...");
} else
}
else
{
return input;
}
}
public static string DefaultActiveTab(UserConfig userConfig, ImportMode tab)
{
var defaultTab = userConfig.DefaultTab;
var visibleTabs = userConfig.VisibleTabs;
if (visibleTabs.Contains(tab) && tab == defaultTab)
{
return "active";
}
else if (!visibleTabs.Contains(tab))
{
return "d-none";
}
return "";
}
public static string DefaultActiveTabContent(UserConfig userConfig, ImportMode tab)
{
var defaultTab = userConfig.DefaultTab;
if (tab == defaultTab)
{
return "show active";
}
return "";
}
public static string DefaultTabSelected(UserConfig userConfig, ImportMode tab)
{
var defaultTab = userConfig.DefaultTab;
var visibleTabs = userConfig.VisibleTabs;
if (!visibleTabs.Contains(tab))
{
return "disabled";
}
else if (tab == defaultTab)
{
return "selected";
}
return "";
}
}
}

View File

@ -3,9 +3,9 @@ using CsvHelper.Configuration;
namespace CarCareTracker.MapProfile
{
public class FuellyMapper: ClassMap<ImportModel>
public class ImportMapper: ClassMap<ImportModel>
{
public FuellyMapper()
public ImportMapper()
{
Map(m => m.Date).Name(["date", "fuelup_date"]);
Map(m => m.Odometer).Name(["odometer"]);
@ -17,6 +17,9 @@ namespace CarCareTracker.MapProfile
Map(m => m.IsFillToFull).Name(["isfilltofull", "filled up"]);
Map(m => m.Description).Name(["description"]);
Map(m => m.MissedFuelUp).Name(["missed_fuelup", "missedfuelup"]);
Map(m => m.PartSupplier).Name(["partsupplier"]);
Map(m => m.PartQuantity).Name(["partquantity"]);
Map(m => m.PartNumber).Name(["partnumber"]);
}
}
}

View File

@ -15,6 +15,20 @@
public string PartialFuelUp { get; set; }
public string IsFillToFull { get; set; }
public string MissedFuelUp { get; set; }
public string PartNumber { get; set; }
public string PartSupplier { get; set; }
public string PartQuantity { get; set; }
}
public class SupplyRecordExportModel
{
public string Date { get; set; }
public string PartNumber { get; set; }
public string PartSupplier { get; set; }
public string PartQuantity { get; set; }
public string Description { get; set; }
public string Cost { get; set; }
public string Notes { get; set; }
}
public class ServiceRecordExportModel

View File

@ -0,0 +1,37 @@
namespace CarCareTracker.Models
{
public class SupplyRecord
{
public int Id { get; set; }
public int VehicleId { get; set; }
/// <summary>
/// When the part or supplies were purchased.
/// </summary>
public DateTime Date { get; set; }
/// <summary>
/// Part number can be alphanumeric.
/// </summary>
public string PartNumber { get; set; }
/// <summary>
/// Where the part/supplies were purchased from.
/// </summary>
public string PartSupplier { get; set; }
/// <summary>
/// Amount purchased, can be partial quantities such as fluids.
/// </summary>
public decimal Quantity { get; set; }
/// <summary>
/// Description of the part/supplies purchased.
/// </summary>
public string Description { get; set; }
/// <summary>
/// How much it costs
/// </summary>
public decimal Cost { get; set; }
/// <summary>
/// Additional notes.
/// </summary>
public string Notes { get; set; }
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
}
}

View File

@ -0,0 +1,27 @@
namespace CarCareTracker.Models
{
public class SupplyRecordInput
{
public int Id { get; set; }
public int VehicleId { get; set; }
public string Date { get; set; } = DateTime.Now.ToShortDateString();
public string PartNumber { get; set; }
public string PartSupplier { get; set; }
public decimal Quantity { 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 SupplyRecord ToSupplyRecord() { return new SupplyRecord {
Id = Id,
VehicleId = VehicleId,
Date = DateTime.Parse(Date),
Cost = Cost,
PartNumber = PartNumber,
PartSupplier = PartSupplier,
Quantity = Quantity,
Description = Description,
Notes = Notes,
Files = Files }; }
}
}

View File

@ -12,5 +12,15 @@
public bool UseThreeDecimalGasCost { get; set; }
public string UserNameHash { get; set; }
public string UserPasswordHash { get; set;}
public List<ImportMode> VisibleTabs { get; set; } = new List<ImportMode>() {
ImportMode.Dashboard,
ImportMode.ServiceRecord,
ImportMode.RepairRecord,
ImportMode.GasRecord,
ImportMode.UpgradeRecord,
ImportMode.TaxRecord,
ImportMode.ReminderRecord,
ImportMode.NoteRecord};
public ImportMode DefaultTab { get; set; } = ImportMode.Dashboard;
}
}

View File

@ -22,6 +22,7 @@ builder.Services.AddSingleton<IUserRecordDataAccess, UserRecordDataAccess>();
builder.Services.AddSingleton<ITokenRecordDataAccess, TokenRecordDataAccess>();
builder.Services.AddSingleton<IUserAccessDataAccess, UserAccessDataAccess>();
builder.Services.AddSingleton<IUserConfigDataAccess, UserConfigDataAccess>();
builder.Services.AddSingleton<ISupplyRecordDataAccess, SupplyRecordDataAccess>();
//configure helpers
builder.Services.AddSingleton<IFileHelper, FileHelper>();

View File

@ -1,4 +1,5 @@
@model UserConfig
@using CarCareTracker.Helper
@model UserConfig
<div class="row">
<div class="d-flex justify-content-center">
@ -22,8 +23,6 @@
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useUKMPG" checked="@Model.UseUKMPG">
<label class="form-check-label" for="useUKMPG">Use UK MPG Calculation<br /><small class="text-body-secondary">Input Gas Consumption in Liters, it will be converted to UK Gals for MPG Calculation</small></label>
</div>
</div>
<div class="col-12 col-md-6">
<div class="form-check form-switch">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useDescending" checked="@Model.UseDescending">
<label class="form-check-label" for="useDescending">Sort lists in Descending Order(Newest to Oldest)</label>
@ -44,6 +43,88 @@
</div>
}
</div>
<div class="col-12 col-md-6">
<div class="row" id="visibleTabs">
<div class="col-12">
<span class="lead">Visible Tabs</span>
</div>
<div class="col-12 col-md-6">
<ul class="list-group">
<li class="list-group-item">
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="ServiceRecord" id="serviceRecordTab" @(Model.VisibleTabs.Contains(ImportMode.ServiceRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="serviceRecordTab">Service Records</label>
</li>
<li class="list-group-item d-none">
<input onChange="updateSettings()" disabled class="form-check-input me-1" type="checkbox" value="Dashboard" id="dashboardTab" @(Model.VisibleTabs.Contains(ImportMode.Dashboard) ? "checked" : "")>
<label class="form-check-label stretched-link" for="dashboardTab">Dashboard</label>
</li>
<li class="list-group-item">
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="RepairRecord" id="repairRecordTab" @(Model.VisibleTabs.Contains(ImportMode.RepairRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="repairRecordTab">Repairs</label>
</li>
<li class="list-group-item">
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="UpgradeRecord" id="upgradeRecordTab" @(Model.VisibleTabs.Contains(ImportMode.UpgradeRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="upgradeRecordTab">Upgrades</label>
</li>
<li class="list-group-item">
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="GasRecord" id="gasRecordTab" @(Model.VisibleTabs.Contains(ImportMode.GasRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="gasRecordTab">Fuel</label>
</li>
</ul>
</div>
<div class="col-12 col-md-6">
<ul class="list-group">
<li class="list-group-item">
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="TaxRecord" id="taxRecordTab" @(Model.VisibleTabs.Contains(ImportMode.TaxRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="taxRecordTab">Taxes</label>
</li>
<li class="list-group-item">
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="NoteRecord" id="noteRecordTab" @(Model.VisibleTabs.Contains(ImportMode.NoteRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="noteRecordTab">Notes</label>
</li>
<li class="list-group-item">
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="ReminderRecord" id="reminderRecordTab" @(Model.VisibleTabs.Contains(ImportMode.ReminderRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="reminderRecordTab">Reminder</label>
</li>
<li class="list-group-item">
<input onChange="updateSettings()" class="form-check-input me-1" type="checkbox" value="SupplyRecord" id="supplyRecordTab" @(Model.VisibleTabs.Contains(ImportMode.SupplyRecord) ? "checked" : "")>
<label class="form-check-label stretched-link" for="supplyRecordTab">Supplies</label>
</li>
</ul>
</div>
</div>
<div class="row">
<div class="col-12 col-md-6">
<span class="lead">Default Tab</span>
<select class="form-select" onchange="updateSettings()" id="defaultTab">
<!option @(StaticHelper.DefaultTabSelected(Model, ImportMode.Dashboard)) value="Dashboard">Dashboard</!option>
<!option @(StaticHelper.DefaultTabSelected(Model,ImportMode.ServiceRecord)) value="ServiceRecord">Service Record</!option>
<!option @(StaticHelper.DefaultTabSelected(Model, ImportMode.RepairRecord)) value="RepairRecord">Repairs</!option>
<!option @(StaticHelper.DefaultTabSelected(Model, ImportMode.UpgradeRecord)) value="UpgradeRecord">Upgrades</!option>
<!option @(StaticHelper.DefaultTabSelected(Model, ImportMode.GasRecord)) value="GasRecord">Fuel</!option>
<!option @(StaticHelper.DefaultTabSelected(Model, ImportMode.TaxRecord)) value="TaxRecord">Tax</!option>
<!option @(StaticHelper.DefaultTabSelected(Model, ImportMode.NoteRecord)) value="NoteRecord">Notes</!option>
<!option @(StaticHelper.DefaultTabSelected(Model, ImportMode.ReminderRecord)) value="ReminderRecord">Reminders</!option>
<!option @(StaticHelper.DefaultTabSelected(Model, ImportMode.SupplyRecord)) value="SupplyRecord">Supplies</!option>
</select>
</div>
@if (User.IsInRole(nameof(UserData.IsRootUser)))
{
<div class="col-12 col-md-6">
<span class="lead">Backups</span>
<div class="row">
<div class="col-6 d-grid">
<button onclick="makeBackup()" class="btn btn-primary btn-md">Make</button>
</div>
<div class="col-6 d-grid">
<input onChange="restoreBackup(this)" type="file" accept=".zip" class="d-none" id="inputBackup">
<button onclick="openRestoreBackup()" class="btn btn-secondary btn-md">Restore</button>
</div>
</div>
</div>
}
</div>
</div>
</div>
<div class="row">
<div class="d-flex justify-content-center">
@ -86,7 +167,18 @@
</div>
</div>
<script>
function getCheckedTabs() {
var visibleTabs = $("#visibleTabs :checked").map(function () {
return this.value;
});
return visibleTabs.toArray();
}
function updateSettings(){
var visibleTabs = getCheckedTabs();
var defaultTab = $("#defaultTab").val();
if (!visibleTabs.includes(defaultTab)){
defaultTab = "Dashboard"; //default to dashboard.
}
var userConfigObject = {
useDarkMode: $("#enableDarkMode").is(':checked'),
enableCsvImports: $("#enableCsvImports").is(':checked'),
@ -94,9 +186,13 @@
useDescending: $("#useDescending").is(':checked'),
hideZero: $("#hideZero").is(":checked"),
useUKMpg: $("#useUKMPG").is(":checked"),
useThreeDecimalGasCost: $("#useThreeDecimal").is(":checked")
useThreeDecimalGasCost: $("#useThreeDecimal").is(":checked"),
visibleTabs: visibleTabs,
defaultTab: defaultTab
}
sloader.show();
$.post('/Home/WriteToSettings', { userConfig: userConfigObject}, function(data){
sloader.hide();
if (data) {
setTimeout(function () { window.location.href = '/Home/Index?tab=settings' }, 500);
} else {
@ -104,6 +200,40 @@
}
})
}
function makeBackup() {
$.get('/Files/MakeBackup', function (data) {
window.location.href = data;
});
}
function openRestoreBackup(){
$("#inputBackup").click();
}
function restoreBackup(event) {
let formData = new FormData();
formData.append("file", event.files[0]);
sloader.show();
$.ajax({
url: "/Files/HandleFileUpload",
data: formData,
cache: false,
processData: false,
contentType: false,
type: 'POST',
success: function (response) {
if (response.trim() != '') {
$.post('/Files/RestoreBackup', { fileName : response}, function (data) {
sloader.hide();
if (data){
successToast("Backup Restored");
setTimeout(function () { window.location.href = '/Home/Index' }, 500);
} else {
errorToast("An error occurred, please try again later.");
}
});
}
}
});
}
function enableAuthCheckChanged(){
var enableAuth = $("#enableAuth").is(":checked");
if (enableAuth) {

View File

@ -1,6 +1,11 @@
@{
@using CarCareTracker.Helper
@{
ViewData["Title"] = "LubeLogger - View Vehicle";
}
@inject IConfigHelper config
@{
var userConfig = config.GetUserConfig(User);
}
@model Vehicle
@section Scripts {
<script src="~/js/vehicle.js" asp-append-version="true"></script>
@ -12,6 +17,7 @@
<script src="~/js/upgraderecord.js" asp-append-version="true"></script>
<script src="~/js/note.js" asp-append-version="true"></script>
<script src="~/js/reports.js" asp-append-version="true"></script>
<script src="~/js/supplyrecord.js" asp-append-version="true"></script>
<script src="~/lib/chart-js/chart.umd.js"></script>
}
<div class="lubelogger-mobile-nav" onclick="hideMobileNav()">
@ -23,28 +29,31 @@
<button class="nav-link" onclick="editVehicle(@Model.Id)"><span class="display-3 ms-2"><i class="bi bi-pencil-square"></i>Edit Vehicle</span></button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link active" id="report-tab" data-bs-toggle="tab" data-bs-target="#report-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-file-bar-graph me-2"></i>Dashboard</span></button>
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.Dashboard)" id="report-tab" data-bs-toggle="tab" data-bs-target="#report-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-file-bar-graph me-2"></i>Dashboard</span></button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="servicerecord-tab" data-bs-toggle="tab" data-bs-target="#servicerecord-tab-pane" type="button" role="tab" aria-selected="true"><span class="display-3 ms-2"><i class="bi bi-card-checklist me-2"></i>Service Records</span></button>
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.ServiceRecord)" id="servicerecord-tab" data-bs-toggle="tab" data-bs-target="#servicerecord-tab-pane" type="button" role="tab" aria-selected="true"><span class="display-3 ms-2"><i class="bi bi-card-checklist me-2"></i>Service Records</span></button>
</li>
<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"><span class="display-3 ms-2"><i class="bi bi-exclamation-octagon me-2"></i>Repairs</span></button>
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.RepairRecord)" id="accident-tab" data-bs-toggle="tab" data-bs-target="#accident-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-exclamation-octagon me-2"></i>Repairs</span></button>
</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"><span class="display-3 ms-2"><i class="bi bi-wrench-adjustable me-2"></i>Upgrades</span></button>
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.UpgradeRecord)" id="upgrade-tab" data-bs-toggle="tab" data-bs-target="#upgrade-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-wrench-adjustable me-2"></i>Upgrades</span></button>
</li>
<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"><span class="display-3 ms-2"><i class="bi bi-fuel-pump me-2"></i>Fuel</span></button>
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.GasRecord)" id="gas-tab" data-bs-toggle="tab" data-bs-target="#gas-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-fuel-pump me-2"></i>Fuel</span></button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="tax-tab" data-bs-toggle="tab" data-bs-target="#tax-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-currency-dollar me-2"></i>Taxes</span></button>
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.SupplyRecord)" id="supply-tab" data-bs-toggle="tab" data-bs-target="#supply-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-shop me-2"></i>Supplies</span></button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="notes-tab" data-bs-toggle="tab" data-bs-target="#notes-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-journal-bookmark me-2"></i>Notes</span></button>
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.TaxRecord)" id="tax-tab" data-bs-toggle="tab" data-bs-target="#tax-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-currency-dollar me-2"></i>Taxes</span></button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="reminder-tab" data-bs-toggle="tab" data-bs-target="#reminder-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><div class="reminderBellDiv" style="display:inline-flex;"><i class="reminderBell bi bi-bell me-2"></i></div>Reminders</span></button>
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.NoteRecord)" id="notes-tab" data-bs-toggle="tab" data-bs-target="#notes-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><i class="bi bi-journal-bookmark me-2"></i>Notes</span></button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.ReminderRecord)" id="reminder-tab" data-bs-toggle="tab" data-bs-target="#reminder-tab-pane" type="button" role="tab" aria-selected="false"><span class="display-3 ms-2"><div class="reminderBellDiv" style="display:inline-flex;"><i class="reminderBell bi bi-bell me-2"></i></div>Reminders</span></button>
</li>
<li class="nav-item" role="presentation">
<button onclick="deleteVehicle(@Model.Id)" class="dropdown-item"><span class="display-3 ms-2"><i class="bi bi-trash me-2"></i>Delete Vehicle</span></button>
@ -65,28 +74,31 @@
<hr />
<ul class="nav nav-tabs lubelogger-tab" id="vehicleTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="report-tab" data-bs-toggle="tab" data-bs-target="#report-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-file-bar-graph me-2"></i>Dash</button>
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.Dashboard)" id="report-tab" data-bs-toggle="tab" data-bs-target="#report-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-file-bar-graph me-2"></i>Dash</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="servicerecord-tab" data-bs-toggle="tab" data-bs-target="#servicerecord-tab-pane" type="button" role="tab" aria-selected="true"><i class="bi bi-card-checklist me-2"></i>Service Records</button>
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.ServiceRecord)" id="servicerecord-tab" data-bs-toggle="tab" data-bs-target="#servicerecord-tab-pane" type="button" role="tab" aria-selected="true"><i class="bi bi-card-checklist me-2"></i>Service Records</button>
</li>
<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 @StaticHelper.DefaultActiveTab(userConfig, ImportMode.RepairRecord)" 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 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>
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.UpgradeRecord)" 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">
<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 @StaticHelper.DefaultActiveTab(userConfig, ImportMode.GasRecord)" 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 class="nav-item" role="presentation">
<button class="nav-link" id="tax-tab" data-bs-toggle="tab" data-bs-target="#tax-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-currency-dollar me-2"></i>Taxes</button>
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.SupplyRecord)" id="supply-tab" data-bs-toggle="tab" data-bs-target="#supply-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-shop me-2"></i>Supplies</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="notes-tab" data-bs-toggle="tab" data-bs-target="#notes-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-journal-bookmark me-2"></i>Notes</button>
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.TaxRecord)" id="tax-tab" data-bs-toggle="tab" data-bs-target="#tax-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-currency-dollar me-2"></i>Taxes</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="reminder-tab" data-bs-toggle="tab" data-bs-target="#reminder-tab-pane" type="button" role="tab" aria-selected="false"><div class="reminderBellDiv" style="display:inline-flex;"><i class="reminderBell bi bi-bell me-2"></i></div>Reminders</button>
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.NoteRecord)" id="notes-tab" data-bs-toggle="tab" data-bs-target="#notes-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-journal-bookmark me-2"></i>Notes</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link @StaticHelper.DefaultActiveTab(userConfig, ImportMode.ReminderRecord)" id="reminder-tab" data-bs-toggle="tab" data-bs-target="#reminder-tab-pane" type="button" role="tab" aria-selected="false"><div class="reminderBellDiv" style="display:inline-flex;"><i class="reminderBell bi bi-bell me-2"></i></div>Reminders</button>
</li>
<li class="nav-item dropdown ms-auto" role="presentation">
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false">Manage Vehicle</a>
@ -96,14 +108,15 @@
</li>
</ul>
<div class="tab-content" id="vehicleTabContent">
<div class="tab-pane fade" 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="tax-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade" id="notes-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 show active" id="report-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade" id="upgrade-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.ServiceRecord)" id="servicerecord-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.GasRecord)" id="gas-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.TaxRecord)" id="tax-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.NoteRecord)" id="notes-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.RepairRecord)" id="accident-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.ReminderRecord)" id="reminder-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.Dashboard)" id="report-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.UpgradeRecord)" id="upgrade-tab-pane" role="tabpanel" tabindex="0"></div>
<div class="tab-pane fade @StaticHelper.DefaultActiveTabContent(userConfig, ImportMode.SupplyRecord)" id="supply-tab-pane" role="tabpanel" tabindex="0"></div>
</div>
</div>
<div class="modal fade" id="editVehicleModal" tabindex="-1" role="dialog">
@ -128,5 +141,8 @@
function GetVehicleId() {
return { vehicleId: @Model.Id};
}
function GetDefaultTab() {
return { tab: "@userConfig.DefaultTab" };
}
bindWindowResize();
</script>

View File

@ -25,6 +25,9 @@
} else if (Model == ImportMode.TaxRecord)
{
<a class="btn btn-link" href="/defaults/taxrecordsample.csv" target="_blank">Download Sample</a>
} else if (Model == ImportMode.SupplyRecord)
{
<a class="btn btn-link" href="/defaults/supplysample.csv" target="_blank">Download Sample</a>
}
</div>
</div>
@ -62,6 +65,8 @@
getVehicleTaxRecords(vehicleId);
} else if (mode == "UpgradeRecord") {
getVehicleUpgradeRecords(vehicleId);
} else if (mode == "SupplyRecord") {
getVehicleSupplyRecords(vehicleId);
}
} else {
errorToast("An error has occurred, please double check the data and try again.");

View File

@ -9,7 +9,7 @@
new Chart($("#pie-chart"), {
type: 'pie',
data: {
labels: ["Planned Maintenance(Service Records)", "Unplanned Maintenance(Repairs)", "Upgrades", "Tax", "Fuel"],
labels: ["Service Records", "Repairs", "Upgrades", "Tax", "Fuel"],
datasets: [
{
label: "Expenses by Category",

View File

@ -0,0 +1,85 @@
@model SupplyRecordInput
@{
var isNew = Model.Id == 0;
}
<div class="modal-header">
<h5 class="modal-title">@(isNew ? "Add New Supply Record" : "Edit Supply Record")</h5>
<button type="button" class="btn-close" onclick="hideAddSupplyRecordModal()" 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="supplyRecordDate">Date</label>
<div class="input-group">
<input type="text" id="supplyRecordDate" class="form-control" placeholder="Date purchased" value="@Model.Date">
<span class="input-group-text"><i class="bi bi-calendar-event"></i></span>
</div>
<label for="supplyRecordPartNumber">Part Number</label>
<input type="text" id="supplyRecordPartNumber" class="form-control" placeholder="Part #/Model #/SKU #" value="@(isNew ? "" : Model.PartNumber)">
<label for="supplyRecordDescription">Description</label>
<input type="text" id="supplyRecordDescription" class="form-control" placeholder="Description of the Part/Supplies" value="@Model.Description">
<label for="supplyRecordSupplier">Supplier/Vendor</label>
<input type="text" id="supplyRecordSupplier" class="form-control" placeholder="Part Supplier" value="@Model.PartSupplier">
<div class="row">
<div class="col-md-6 col-12">
<label for="supplyRecordQuantity">Quantity</label>
<input type="text" id="supplyRecordQuantity" class="form-control" placeholder="Quantity" value="@(isNew ? "1" : Model.Quantity)">
</div>
<div class="col-md-6 col-12">
<label for="supplyRecordCost">Cost</label>
<input type="text" id="supplyRecordCost" class="form-control" placeholder="Cost" value="@(isNew ? "" : Model.Cost)">
</div>
</div>
</div>
<div class="col-md-6 col-12">
<label for="supplyRecordNotes">Notes(optional)</label>
<textarea id="supplyRecordNotes" class="form-control" rows="5">@Model.Notes</textarea>
@if (Model.Files.Any())
{
<div>
@await Html.PartialAsync("_UploadedFiles", Model.Files)
<label for="supplyRecordFiles">Upload more documents</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="supplyRecordFiles">
</div>
}
else
{
<label for="supplyRecordFiles">Upload documents(optional)</label>
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept=".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx" class="form-control-file" id="supplyRecordFiles">
}
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
@if (!isNew)
{
<button type="button" class="btn btn-danger" onclick="deleteSupplyRecord(@Model.Id)" style="margin-right:auto;">Delete</button>
}
<button type="button" class="btn btn-secondary" onclick="hideAddSupplyRecordModal()">Cancel</button>
@if (isNew)
{
<button type="button" class="btn btn-primary" onclick="saveSupplyRecordToVehicle()">Add New Supply Record</button>
}
else if (!isNew)
{
<button type="button" class="btn btn-primary" onclick="saveSupplyRecordToVehicle(true)">Edit Supply Record</button>
}
</div>
<script>
var uploadedFiles = [];
getUploadedFilesFromModel();
function getUploadedFilesFromModel() {
@foreach (UploadedFiles filesUploaded in Model.Files)
{
@:uploadedFiles.push({ name: "@filesUploaded.Name", location: "@filesUploaded.Location" });
}
}
function getSupplyRecordModelData() {
return { id: @Model.Id}
}
</script>

View File

@ -0,0 +1,78 @@
@using CarCareTracker.Helper
@inject IConfigHelper config
@{
var enableCsvImports = config.GetUserConfig(User).EnableCsvImports;
var hideZero = config.GetUserConfig(User).HideZero;
}
@model List<SupplyRecord>
<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 Supply 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="showAddSupplyRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Supply 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('SupplyRecord')">Import via CSV</a></li>
<li><a class="dropdown-item" href="#" onclick="exportVehicleData('SupplyRecord')">Export to CSV</a></li>
</ul>
</div>
}
else
{
<button onclick="showAddSupplyRecordModal()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>Add Supply Record</button>
}
</div>
</div>
</div>
<div class="row vehicleDetailTabContainer">
<div class="col-12">
<div class="row mt-2 showOnPrint">
<div class="d-flex">
<img src="/defaults/lubelogger_logo.png" />
</div>
</div>
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-1">Date</th>
<th scope="col" class="col-2">Part #</th>
<th scope="col" class="col-2">Supplier</th>
<th scope="col" class="col-3">Description</th>
<th scope="col" class="col-1">Quantity</th>
<th scope="col" class="col-1">Cost</th>
<th scope="col" class="col-2">Notes</th>
</tr>
</thead>
<tbody>
@foreach (SupplyRecord supplyRecord in Model)
{
<tr class="d-flex" style="cursor:pointer;" onclick="showEditSupplyRecordModal(@supplyRecord.Id)">
<td class="col-1">@supplyRecord.Date.ToShortDateString()</td>
<td class="col-2">@supplyRecord.PartNumber</td>
<td class="col-2">@supplyRecord.PartSupplier</td>
<td class="col-3">@supplyRecord.Description</td>
<td class="col-1">@supplyRecord.Quantity</td>
<td class="col-1">@((hideZero && supplyRecord.Cost == default) ? "---" : supplyRecord.Cost.ToString("C"))</td>
<td class="col-2 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(supplyRecord.Notes)</td>
</tr>
}
</tbody>
</table>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="supplyRecordModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="supplyRecordModalContent">
</div>
</div>
</div>

View File

@ -14,6 +14,8 @@
"HideZero": false,
"UseUKMPG": false,
"UseThreeDecimalGasCost": true,
"VisibleTabs": [ 0, 1, 4, 2, 3, 6, 5, 8 ],
"DefaultTab": 8,
"UserNameHash": "",
"UserPasswordHash": ""
}

View File

@ -0,0 +1,2 @@
Date,PartNumber,PartSupplier,PartQuantity,Description,Cost,Notes
1/16/2024,EVA17872045551,Evan Fischer,1,Front Bumper,$95.14,Altima Activities
1 Date PartNumber PartSupplier PartQuantity Description Cost Notes
2 1/16/2024 ‎EVA17872045551 Evan Fischer 1 Front Bumper $95.14 Altima Activities

121
wwwroot/js/supplyrecord.js Normal file
View File

@ -0,0 +1,121 @@
function showAddSupplyRecordModal() {
$.get('/Vehicle/GetAddSupplyRecordPartialView', function (data) {
if (data) {
$("#supplyRecordModalContent").html(data);
//initiate datepicker
initDatePicker($('#supplyRecordDate'));
$('#supplyRecordModal').modal('show');
}
});
}
function showEditSupplyRecordModal(supplyRecordId) {
$.get(`/Vehicle/GetSupplyRecordForEditById?supplyRecordId=${supplyRecordId}`, function (data) {
if (data) {
$("#supplyRecordModalContent").html(data);
//initiate datepicker
initDatePicker($('#supplyRecordDate'));
$('#supplyRecordModal').modal('show');
}
});
}
function hideAddSupplyRecordModal() {
$('#supplyRecordModal').modal('hide');
}
function deleteSupplyRecord(supplyRecordId) {
$("#workAroundInput").show();
Swal.fire({
title: "Confirm Deletion?",
text: "Deleted Supply Records cannot be restored.",
showCancelButton: true,
confirmButtonText: "Delete",
confirmButtonColor: "#dc3545"
}).then((result) => {
if (result.isConfirmed) {
$.post(`/Vehicle/DeleteSupplyRecordById?supplyRecordId=${supplyRecordId}`, function (data) {
if (data) {
hideAddSupplyRecordModal();
successToast("Supply Record Deleted");
var vehicleId = GetVehicleId().vehicleId;
getVehicleSupplyRecords(vehicleId);
} else {
errorToast("An error has occurred, please try again later.");
}
});
} else {
$("#workAroundInput").hide();
}
});
}
function saveSupplyRecordToVehicle(isEdit) {
//get values
var formValues = getAndValidateSupplyRecordValues();
//validate
if (formValues.hasError) {
errorToast("Please check the form data");
return;
}
//save to db.
$.post('/Vehicle/SaveSupplyRecordToVehicleId', { supplyRecord: formValues }, function (data) {
if (data) {
successToast(isEdit ? "Supply Record Updated" : "Supply Record Added.");
hideAddSupplyRecordModal();
saveScrollPosition();
getVehicleSupplyRecords(formValues.vehicleId);
if (formValues.addReminderRecord) {
setTimeout(function () { showAddReminderModal(formValues); }, 500);
}
} else {
errorToast("An error has occurred, please try again later.");
}
})
}
function getAndValidateSupplyRecordValues() {
var supplyDate = $("#supplyRecordDate").val();
var supplyPartNumber = $("#supplyRecordPartNumber").val();
var supplyDescription = $("#supplyRecordDescription").val();
var supplySupplier = $("#supplyRecordSupplier").val();
var supplyQuantity = $("#supplyRecordQuantity").val();
var supplyCost = $("#supplyRecordCost").val();
var supplyNotes = $("#supplyRecordNotes").val();
var vehicleId = GetVehicleId().vehicleId;
var supplyRecordId = getSupplyRecordModelData().id;
//validation
var hasError = false;
if (supplyDate.trim() == '') { //eliminates whitespace.
hasError = true;
$("#supplyRecordDate").addClass("is-invalid");
} else {
$("#supplyRecordDate").removeClass("is-invalid");
}
if (supplyDescription.trim() == '') {
hasError = true;
$("#supplyRecordDescription").addClass("is-invalid");
} else {
$("#supplyRecordDescription").removeClass("is-invalid");
}
if (supplyQuantity.trim() == '' || !isValidMoney(supplyQuantity) || parseFloat(supplyQuantity) < 0) {
hasError = true;
$("#supplyRecordQuantity").addClass("is-invalid");
} else {
$("#supplyRecordQuantity").removeClass("is-invalid");
}
if (supplyCost.trim() == '' || !isValidMoney(supplyCost)) {
hasError = true;
$("#supplyRecordCost").addClass("is-invalid");
} else {
$("#supplyRecordCost").removeClass("is-invalid");
}
return {
id: supplyRecordId,
hasError: hasError,
vehicleId: vehicleId,
date: supplyDate,
partNumber: supplyPartNumber,
partSupplier: supplySupplier,
description: supplyDescription,
cost: supplyCost,
notes: supplyNotes,
quantity: supplyQuantity,
files: uploadedFiles
}
}

View File

@ -30,6 +30,9 @@ $(document).ready(function () {
case "upgrade-tab":
getVehicleUpgradeRecords(vehicleId);
break;
case "supply-tab":
getVehicleSupplyRecords(vehicleId);
break;
}
switch (e.relatedTarget.id) { //clear out previous tabs with grids in them to help with performance
case "servicerecord-tab":
@ -56,9 +59,41 @@ $(document).ready(function () {
case "notes-tab":
$("#notes-tab-pane").html("");
break;
case "supply-tab":
$("#supply-tab-pane").html("");
break;
}
});
getVehicleReport(vehicleId);
var defaultTab = GetDefaultTab().tab;
switch (defaultTab) {
case "ServiceRecord":
getVehicleServiceRecords(vehicleId);
break;
case "NoteRecord":
getVehicleNotes(vehicleId);
break;
case "GasRecord":
getVehicleGasRecords(vehicleId);
break;
case "RepairRecord":
getVehicleCollisionRecords(vehicleId);
break;
case "TaxRecord":
getVehicleTaxRecords(vehicleId);
break;
case "Dashboard":
getVehicleReport(vehicleId);
break;
case "ReminderRecord":
getVehicleReminders(vehicleId);
break;
case "UpgradeRecord":
getVehicleUpgradeRecords(vehicleId);
break;
case "SupplyRecord":
getVehicleSupplyRecords(vehicleId);
break;
}
});
function getVehicleNotes(vehicleId) {
@ -78,6 +113,15 @@ function getVehicleServiceRecords(vehicleId) {
}
});
}
function getVehicleSupplyRecords(vehicleId) {
$.get(`/Vehicle/GetSupplyRecordsByVehicleId?vehicleId=${vehicleId}`, function (data) {
if (data) {
$("#supply-tab-pane").html(data);
restoreScrollPosition();
getVehicleHaveImportantReminders(vehicleId);
}
});
}
function getVehicleUpgradeRecords(vehicleId) {
$.get(`/Vehicle/GetUpgradeRecordsByVehicleId?vehicleId=${vehicleId}`, function (data) {
if (data) {