Merge branch 'main' into Hargata/documentation

This commit is contained in:
DESKTOP-T0O5CDB\DESK-555BD
2024-01-25 17:01:31 -07:00
32 changed files with 323 additions and 42 deletions

View File

@@ -22,10 +22,13 @@ namespace CarCareTracker.Controllers
private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
private readonly IUpgradeRecordDataAccess _upgradeRecordDataAccess;
private readonly IOdometerRecordDataAccess _odometerRecordDataAccess;
private readonly IUserAccessDataAccess _userAccessDataAccess;
private readonly IUserRecordDataAccess _userRecordDataAccess;
private readonly IReminderHelper _reminderHelper;
private readonly IGasHelper _gasHelper;
private readonly IUserLogic _userLogic;
private readonly IFileHelper _fileHelper;
private readonly IMailHelper _mailHelper;
public APIController(IVehicleDataAccess dataAccess,
IGasHelper gasHelper,
IReminderHelper reminderHelper,
@@ -37,6 +40,9 @@ namespace CarCareTracker.Controllers
IReminderRecordDataAccess reminderRecordDataAccess,
IUpgradeRecordDataAccess upgradeRecordDataAccess,
IOdometerRecordDataAccess odometerRecordDataAccess,
IUserAccessDataAccess userAccessDataAccess,
IUserRecordDataAccess userRecordDataAccess,
IMailHelper mailHelper,
IFileHelper fileHelper,
IUserLogic userLogic)
{
@@ -49,6 +55,9 @@ namespace CarCareTracker.Controllers
_reminderRecordDataAccess = reminderRecordDataAccess;
_upgradeRecordDataAccess = upgradeRecordDataAccess;
_odometerRecordDataAccess = odometerRecordDataAccess;
_userAccessDataAccess = userAccessDataAccess;
_userRecordDataAccess = userRecordDataAccess;
_mailHelper = mailHelper;
_gasHelper = gasHelper;
_reminderHelper = reminderHelper;
_userLogic = userLogic;
@@ -428,12 +437,65 @@ namespace CarCareTracker.Controllers
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpGet]
[Route("/api/vehicle/reminders/send")]
public IActionResult SendReminders(List<ReminderUrgency> urgencies)
{
var vehicles = _dataAccess.GetVehicles();
List<OperationResponse> operationResponses = new List<OperationResponse>();
foreach(Vehicle vehicle in vehicles)
{
var vehicleId = vehicle.Id;
//get reminders
var currentMileage = GetMaxMileage(vehicleId);
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId);
var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now).OrderByDescending(x => x.Urgency).ToList();
results.RemoveAll(x => !urgencies.Contains(x.Urgency));
if (!results.Any())
{
continue;
}
//get list of recipients.
var userIds = _userAccessDataAccess.GetUserAccessByVehicleId(vehicleId).Select(x => x.Id.UserId);
List<string> emailRecipients = new List<string>();
foreach (int userId in userIds)
{
var userData = _userRecordDataAccess.GetUserRecordById(userId);
emailRecipients.Add(userData.EmailAddress);
};
if (!emailRecipients.Any())
{
continue;
}
var result = _mailHelper.NotifyUserForReminders(vehicle, emailRecipients, results);
operationResponses.Add(result);
}
if (operationResponses.All(x => x.Success))
{
return Json(new OperationResponse { Success = true, Message = "Emails sent" });
} else if (operationResponses.All(x => !x.Success))
{
return Json(new OperationResponse { Success = false, Message = "All emails failed, check SMTP settings" });
} else
{
return Json(new OperationResponse { Success = true, Message = "Some emails sent, some failed, check recipient settings" });
}
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpGet]
[Route("/api/makebackup")]
public IActionResult MakeBackup()
{
var result = _fileHelper.MakeBackup();
return Json(result);
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpGet]
[Route("/api/demo/restore")]
public IActionResult RestoreDemo()
{
var result = _fileHelper.RestoreBackup("/defaults/demo_default.zip");
return Json(result);
}
private int GetMaxMileage(int vehicleId)
{
var numbersArray = new List<int>();

View File

@@ -85,6 +85,7 @@ namespace CarCareTracker.Controllers
public IActionResult Index(int vehicleId)
{
var data = _dataAccess.GetVehicleById(vehicleId);
UpdateRecurringTaxes(vehicleId);
return View(data);
}
[HttpGet]
@@ -316,7 +317,6 @@ namespace CarCareTracker.Controllers
var vehicleRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
bool useMPG = _config.GetUserConfig(User).UseMPG;
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
{
@@ -376,7 +376,7 @@ namespace CarCareTracker.Controllers
{
VehicleId = vehicleId,
Date = DateTime.Parse(importModel.Date),
Mileage = int.Parse(importModel.Odometer, NumberStyles.Any),
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
Gallons = decimal.Parse(importModel.FuelConsumed, NumberStyles.Any),
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes
};
@@ -420,7 +420,7 @@ namespace CarCareTracker.Controllers
{
VehicleId = vehicleId,
Date = DateTime.Parse(importModel.Date),
Mileage = int.Parse(importModel.Odometer, NumberStyles.Any),
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Service Record on {importModel.Date}" : importModel.Description,
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any)
@@ -433,7 +433,7 @@ namespace CarCareTracker.Controllers
{
VehicleId = vehicleId,
Date = DateTime.Parse(importModel.Date),
Mileage = int.Parse(importModel.Odometer, NumberStyles.Any),
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes
};
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(convertedRecord);
@@ -463,7 +463,7 @@ namespace CarCareTracker.Controllers
{
VehicleId = vehicleId,
Date = DateTime.Parse(importModel.Date),
Mileage = int.Parse(importModel.Odometer, NumberStyles.Any),
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Repair Record on {importModel.Date}" : importModel.Description,
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any)
@@ -476,7 +476,7 @@ namespace CarCareTracker.Controllers
{
VehicleId = vehicleId,
Date = DateTime.Parse(importModel.Date),
Mileage = int.Parse(importModel.Odometer, NumberStyles.Any),
Mileage = decimal.ToInt32(decimal.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)
@@ -529,8 +529,6 @@ namespace CarCareTracker.Controllers
public IActionResult GetGasRecordsByVehicleId(int vehicleId)
{
var result = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
//need it in ascending order to perform computation.
result = result.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList();
//check if the user uses MPG or Liters per 100km.
var userConfig = _config.GetUserConfig(User);
bool useMPG = userConfig.UseMPG;
@@ -757,6 +755,34 @@ namespace CarCareTracker.Controllers
}
return PartialView("_TaxRecords", result);
}
private void UpdateRecurringTaxes(int vehicleId)
{
var result = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
var recurringFees = result.Where(x => x.IsRecurring);
if (recurringFees.Any())
{
foreach(TaxRecord recurringFee in recurringFees)
{
var newDate = recurringFee.Date.AddMonths((int)recurringFee.RecurringInterval);
if (DateTime.Now > newDate){
recurringFee.IsRecurring = false;
var newRecurringFee = new TaxRecord()
{
VehicleId = recurringFee.VehicleId,
Date = newDate,
Description = recurringFee.Description,
Cost = recurringFee.Cost,
IsRecurring = true,
Notes = recurringFee.Notes,
RecurringInterval = recurringFee.RecurringInterval,
Files = recurringFee.Files
};
_taxRecordDataAccess.SaveTaxRecordToVehicle(recurringFee);
_taxRecordDataAccess.SaveTaxRecordToVehicle(newRecurringFee);
}
}
}
}
[HttpPost]
public IActionResult SaveTaxRecordToVehicleId(TaxRecordInput taxRecord)
{
@@ -783,6 +809,8 @@ namespace CarCareTracker.Controllers
Description = result.Description,
Notes = result.Notes,
VehicleId = result.VehicleId,
IsRecurring = result.IsRecurring,
RecurringInterval = result.RecurringInterval,
Files = result.Files
};
return PartialView("_TaxRecordModal", convertedResult);
@@ -816,7 +844,7 @@ namespace CarCareTracker.Controllers
UpgradeRecordSum = upgradeRecords.Sum(x => x.Cost)
};
//get costbymonth
List<CostForVehicleByMonth> allCosts = new List<CostForVehicleByMonth>();
List<CostForVehicleByMonth> allCosts = StaticHelper.GetBaseLineCosts();
allCosts.AddRange(_reportHelper.GetServiceRecordSum(serviceRecords, 0));
allCosts.AddRange(_reportHelper.GetRepairRecordSum(collisionRecords, 0));
allCosts.AddRange(_reportHelper.GetUpgradeRecordSum(upgradeRecords, 0));
@@ -867,10 +895,17 @@ 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 = StaticHelper.GetBaseLineCostsNoMonthName();
monthlyMileageData.AddRange(mileageData.GroupBy(x => x.MonthId).Select(x => new CostForVehicleByMonth
{
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
MonthId = x.Key,
Cost = x.Average(y => y.MilesPerGallon)
}));
monthlyMileageData = monthlyMileageData.GroupBy(x => x.MonthId).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y=>y.Cost)
}).ToList();
viewModel.FuelMileageForVehicleByMonth = monthlyMileageData;
return PartialView("_Report", viewModel);
@@ -956,7 +991,7 @@ namespace CarCareTracker.Controllers
var gasViewModels = _gasHelper.GetGasRecordViewModels(gasRecords, useMPG, useUKMPG);
if (gasViewModels.Any())
{
averageMPG = _gasHelper.GetAverageGasMileage(gasViewModels);
averageMPG = _gasHelper.GetAverageGasMileage(gasViewModels, useMPG);
}
vehicleHistory.MPG = averageMPG;
//insert servicerecords
@@ -1012,10 +1047,17 @@ namespace CarCareTracker.Controllers
mileageData.RemoveAll(x => DateTime.Parse(x.Date).Year != year);
}
mileageData.RemoveAll(x => x.MilesPerGallon == default);
var monthlyMileageData = mileageData.GroupBy(x => x.MonthId).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
var monthlyMileageData = StaticHelper.GetBaseLineCostsNoMonthName();
monthlyMileageData.AddRange(mileageData.GroupBy(x => x.MonthId).Select(x => new CostForVehicleByMonth
{
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
MonthId = x.Key,
Cost = x.Average(y => y.MilesPerGallon)
}));
monthlyMileageData = monthlyMileageData.GroupBy(x => x.MonthId).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
}).ToList();
return PartialView("_MPGByMonthReport", monthlyMileageData);
}
@@ -1023,7 +1065,7 @@ namespace CarCareTracker.Controllers
[HttpPost]
public IActionResult GetCostByMonthByVehicle(int vehicleId, List<ImportMode> selectedMetrics, int year = 0)
{
List<CostForVehicleByMonth> allCosts = new List<CostForVehicleByMonth>();
List<CostForVehicleByMonth> allCosts = StaticHelper.GetBaseLineCosts();
if (selectedMetrics.Contains(ImportMode.ServiceRecord))
{
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
@@ -1266,6 +1308,7 @@ namespace CarCareTracker.Controllers
public IActionResult GetNotesByVehicleId(int vehicleId)
{
var result = _noteDataAccess.GetNotesByVehicleId(vehicleId);
result = result.OrderByDescending(x => x.Pinned).ToList();
return PartialView("_Notes", result);
}
[HttpPost]

View File

@@ -14,7 +14,9 @@
FifteenThousandMiles = 15000,
TwentyThousandMiles = 20000,
ThirtyThousandMiles = 30000,
FortyThousandMiles = 40000,
FiftyThousandMiles = 50000,
SixtyThousandMiles = 60000,
OneHundredThousandMiles = 100000,
OneHundredFiftyThousandMiles = 150000
}

View File

@@ -2,6 +2,7 @@
{
public enum ReminderMonthInterval
{
OneMonth = 1,
ThreeMonths = 3,
SixMonths = 6,
OneYear = 12,

View File

@@ -5,11 +5,11 @@ namespace CarCareTracker.Helper
public interface IGasHelper
{
List<GasRecordViewModel> GetGasRecordViewModels(List<GasRecord> result, bool useMPG, bool useUKMPG);
string GetAverageGasMileage(List<GasRecordViewModel> results);
string GetAverageGasMileage(List<GasRecordViewModel> results, bool useMPG);
}
public class GasHelper : IGasHelper
{
public string GetAverageGasMileage(List<GasRecordViewModel> results)
public string GetAverageGasMileage(List<GasRecordViewModel> results, bool useMPG)
{
var recordWithCalculatedMPG = results.Where(x => x.MilesPerGallon > 0);
var minMileage = results.Min(x => x.Mileage);
@@ -19,12 +19,18 @@ namespace CarCareTracker.Helper
var totalGallonsConsumed = recordWithCalculatedMPG.Sum(x => x.Gallons);
var deltaMileage = maxMileage - minMileage;
var averageGasMileage = (maxMileage - minMileage) / totalGallonsConsumed;
if (!useMPG)
{
averageGasMileage = 100 / averageGasMileage;
}
return averageGasMileage.ToString("F");
}
return "0";
}
public List<GasRecordViewModel> GetGasRecordViewModels(List<GasRecord> result, bool useMPG, bool useUKMPG)
{
//need to order by to get correct results
result = result.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList();
var computedResults = new List<GasRecordViewModel>();
int previousMileage = 0;
decimal unFactoredConsumption = 0.00M;

View File

@@ -8,6 +8,7 @@ namespace CarCareTracker.Helper
{
OperationResponse NotifyUserForRegistration(string emailAddress, string token);
OperationResponse NotifyUserForPasswordReset(string emailAddress, string token);
OperationResponse NotifyUserForReminders(Vehicle vehicle, List<string> emailAddresses, List<ReminderRecordViewModel> reminders);
}
public class MailHelper : IMailHelper
{
@@ -60,20 +61,62 @@ namespace CarCareTracker.Helper
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
}
}
private bool SendEmail(string emailTo, string emailSubject, string emailBody) {
public OperationResponse NotifyUserForReminders(Vehicle vehicle, List<string> emailAddresses, List<ReminderRecordViewModel> reminders)
{
if (string.IsNullOrWhiteSpace(mailConfig.EmailServer))
{
return new OperationResponse { Success = false, Message = "SMTP Server Not Setup" };
}
if (!emailAddresses.Any())
{
return new OperationResponse { Success = false, Message = "No recipients could be found" };
}
if (!reminders.Any())
{
return new OperationResponse { Success = false, Message = "No reminders could be found" };
}
string emailSubject = $"Vehicle Reminders From LubeLogger - {DateTime.Now.ToShortDateString()}";
//construct html table.
string emailBody = $"<h4>{vehicle.Year} {vehicle.Make} {vehicle.Model} #{vehicle.LicensePlate}</h4><br /><table style='width:100%'><tr><th style='padding:8px;'>Urgency</th><th style='padding:8px;'>Description</th></tr>";
foreach(ReminderRecordViewModel reminder in reminders)
{
emailBody += $"<tr><td style='padding:8px; text-align:center;'>{reminder.Urgency}</td><td style='padding:8px; text-align:center;'>{reminder.Description}</td></tr>";
}
emailBody += "</table>";
try
{
foreach (string emailAddress in emailAddresses)
{
SendEmail(emailAddress, emailSubject, emailBody, true, true);
}
return new OperationResponse { Success = true, Message = "Email Sent!" };
} catch (Exception ex)
{
return new OperationResponse { Success = false, Message = ex.Message };
}
}
private bool SendEmail(string emailTo, string emailSubject, string emailBody, bool isBodyHtml = false, bool useAsync = false) {
string to = emailTo;
string from = mailConfig.EmailFrom;
var server = mailConfig.EmailServer;
MailMessage message = new MailMessage(from, to);
message.Subject = emailSubject;
message.Body = emailBody;
message.IsBodyHtml = isBodyHtml;
SmtpClient client = new SmtpClient(server);
client.EnableSsl = mailConfig.UseSSL;
client.Port = mailConfig.Port;
client.Credentials = new NetworkCredential(mailConfig.Username, mailConfig.Password);
try
{
client.Send(message);
if (useAsync)
{
client.SendMailAsync(message, new CancellationToken());
}
else
{
client.Send(message);
}
return true;
}
catch (Exception ex)

View File

@@ -1,4 +1,5 @@
using CarCareTracker.Models;
using System.Globalization;
namespace CarCareTracker.Helper
{
@@ -63,5 +64,41 @@ namespace CarCareTracker.Helper
}
return "";
}
public static List<CostForVehicleByMonth> GetBaseLineCosts()
{
return new List<CostForVehicleByMonth>()
{
new CostForVehicleByMonth {MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(1), MonthId = 1, Cost = 0M},
new CostForVehicleByMonth {MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(2), MonthId = 2, Cost = 0M},
new CostForVehicleByMonth {MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(3), MonthId = 3, Cost = 0M},
new CostForVehicleByMonth {MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(4), MonthId = 4, Cost = 0M},
new CostForVehicleByMonth {MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(5), MonthId = 5, Cost = 0M},
new CostForVehicleByMonth {MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(6), MonthId = 6, Cost = 0M},
new CostForVehicleByMonth {MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(7), MonthId = 7, Cost = 0M},
new CostForVehicleByMonth {MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(8), MonthId = 8, Cost = 0M},
new CostForVehicleByMonth {MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(9), MonthId = 9, Cost = 0M},
new CostForVehicleByMonth {MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(10), MonthId = 10, Cost = 0M},
new CostForVehicleByMonth {MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(11), MonthId = 11, Cost = 0M},
new CostForVehicleByMonth {MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(12), MonthId = 12, Cost = 0M}
};
}
public static List<CostForVehicleByMonth> GetBaseLineCostsNoMonthName()
{
return new List<CostForVehicleByMonth>()
{
new CostForVehicleByMonth { MonthId = 1, Cost = 0M},
new CostForVehicleByMonth {MonthId = 2, Cost = 0M},
new CostForVehicleByMonth {MonthId = 3, Cost = 0M},
new CostForVehicleByMonth {MonthId = 4, Cost = 0M},
new CostForVehicleByMonth {MonthId = 5, Cost = 0M},
new CostForVehicleByMonth {MonthId = 6, Cost = 0M},
new CostForVehicleByMonth {MonthId = 7, Cost = 0M},
new CostForVehicleByMonth {MonthId = 8, Cost = 0M},
new CostForVehicleByMonth {MonthId = 9, Cost = 0M},
new CostForVehicleByMonth { MonthId = 10, Cost = 0M},
new CostForVehicleByMonth { MonthId = 11, Cost = 0M},
new CostForVehicleByMonth { MonthId = 12, Cost = 0M}
};
}
}
}

View File

@@ -6,5 +6,6 @@
public int VehicleId { get; set; }
public string Description { get; set; }
public string NoteText { get; set; }
public bool Pinned { get; set; }
}
}

View File

@@ -8,6 +8,8 @@
public string Description { get; set; }
public decimal Cost { get; set; }
public string Notes { get; set; }
public bool IsRecurring { get; set; } = false;
public ReminderMonthInterval RecurringInterval { get; set; } = ReminderMonthInterval.OneYear;
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
}
}

View File

@@ -8,7 +8,18 @@
public string Description { get; set; }
public decimal Cost { get; set; }
public string Notes { get; set; }
public bool IsRecurring { get; set; } = false;
public ReminderMonthInterval RecurringInterval { get; set; } = ReminderMonthInterval.ThreeMonths;
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
public TaxRecord ToTaxRecord() { return new TaxRecord { Id = Id, VehicleId = VehicleId, Date = DateTime.Parse(Date), Cost = Cost, Description = Description, Notes = Notes, Files = Files }; }
public TaxRecord ToTaxRecord() { return new TaxRecord {
Id = Id,
VehicleId = VehicleId,
Date = DateTime.Parse(Date),
Cost = Cost,
Description = Description,
Notes = Notes,
IsRecurring = IsRecurring,
RecurringInterval = RecurringInterval,
Files = Files }; }
}
}

View File

@@ -241,6 +241,21 @@
</div>
@if (User.IsInRole(nameof(UserData.IsRootUser)))
{
<div class="row">
<div class="col-1">
GET
</div>
<div class="col-5">
<code>/api/vehicle/reminders/send</code>
</div>
<div class="col-3">
Send reminder emails out to collaborators based on specified urgency.
</div>
<div class="col-3">
(must be root user)<br />
urgencies[]=[NotUrgent,Urgent,VeryUrgent,PastDue]
</div>
</div>
<div class="row">
<div class="col-1">
GET

View File

@@ -154,7 +154,7 @@
<img src="/defaults/lubelogger_logo.png" />
</div>
<div class="d-flex justify-content-center">
<small class="text-body-secondary">Version 1.0.7</small>
<small class="text-body-secondary">Version 1.0.8</small>
</div>
<p class="lead">
Proudly developed in the rural town of Price, Utah by Hargata Softworks.

View File

@@ -12,14 +12,14 @@
labels: ["Service Records", "Repairs", "Upgrades", "Tax", "Fuel"],
datasets: [
{
label: "Expenses by Category",
label: "Expenses by Type",
backgroundColor: ["#003f5c", "#58508d", "#bc5090", "#ff6361", "#ffa600"],
data: [
@Model.ServiceRecordSum,
@Model.CollisionRecordSum,
@Model.UpgradeRecordSum,
@Model.TaxRecordSum,
@Model.GasRecordSum
globalParseFloat('@Model.ServiceRecordSum'),
globalParseFloat('@Model.CollisionRecordSum'),
globalParseFloat('@Model.UpgradeRecordSum'),
globalParseFloat('@Model.TaxRecordSum'),
globalParseFloat('@Model.GasRecordSum')
]
}
]

View File

@@ -41,7 +41,7 @@
<span class="ms-2 badge bg-success">@($"# of Gas Records: {Model.GasRecords.Count()}")</span>
@if (Model.GasRecords.Where(x => x.MilesPerGallon > 0).Any())
{
<span class="ms-2 badge bg-primary">@($"Average Fuel Economy: {gasHelper.GetAverageGasMileage(Model.GasRecords)}")</span>
<span class="ms-2 badge bg-primary">@($"Average Fuel Economy: {gasHelper.GetAverageGasMileage(Model.GasRecords, useMPG)}")</span>
<span class="ms-2 badge bg-primary">@($"Min Fuel Economy: {Model.GasRecords.Where(y => y.MilesPerGallon > 0)?.Min(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
<span class="ms-2 badge bg-primary">@($"Max Fuel Economy: {Model.GasRecords.Max(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
}

View File

@@ -3,7 +3,7 @@
var barGraphColors = new string[] { "#00876c", "#43956e", "#67a371", "#89b177", "#a9be80", "#c8cb8b", "#e6d79b", "#e4c281", "#e3ab6b", "#e2925b", "#e07952", "#db5d4f" };
var sortedByMPG = Model.OrderBy(x => x.Cost).ToList();
}
@if (Model.Any())
@if (Model.Where(x=>x.Cost > 0).Any())
{
<canvas id="bar-chart"></canvas>
<script>
@@ -17,7 +17,7 @@
@foreach (CostForVehicleByMonth gasCost in Model)
{
@:barGraphLabels.push("@gasCost.MonthName");
@:barGraphData.push(@gasCost.Cost);
@:barGraphData.push(globalParseFloat('@gasCost.Cost'));
var index = sortedByMPG.FindIndex(x => x.MonthName == gasCost.MonthName);
@:barGraphColors.push('@barGraphColors[index]');
}

View File

@@ -3,7 +3,7 @@
var barGraphColors = new string[] { "#00876c", "#43956e", "#67a371", "#89b177", "#a9be80", "#c8cb8b", "#e6d79b", "#e4c281", "#e3ab6b", "#e2925b", "#e07952", "#db5d4f" };
var sortedByMPG = Model.OrderByDescending(x => x.Cost).ToList();
}
@if (Model.Any())
@if (Model.Where(x=>x.Cost > 0).Any())
{
<canvas id="bar-chart-mpg"></canvas>
@@ -18,7 +18,7 @@
@foreach (CostForVehicleByMonth gasCost in Model)
{
@:barGraphLabels.push("@gasCost.MonthName");
@:barGraphData.push(@gasCost.Cost);
@:barGraphData.push(globalParseFloat('@gasCost.Cost'));
var index = sortedByMPG.FindIndex(x => x.MonthName == gasCost.MonthName);
@:barGraphColors.push('@barGraphColors[index]');
}

View File

@@ -12,6 +12,10 @@
<div class="row">
<div class="col-12">
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="noteIsPinned" checked="@Model.Pinned">
<label class="form-check-label" for="noteIsPinned">Pinned</label>
</div>
<label for="noteDescription">Description</label>
<input type="text" id="noteDescription" class="form-control" placeholder="Description of the note" value="@(isNew ? "" : Model.Description)">
</div>

View File

@@ -27,7 +27,13 @@
@foreach (Note note in Model)
{
<tr class="d-flex" style="cursor:pointer;" onclick="showEditNoteModal(@note.Id)">
<td class="col-3">@note.Description</td>
@if (note.Pinned)
{
<td class="col-3"><i class='bi bi-pin-fill me-2'></i>@note.Description"</td>
} else
{
<td class="col-3">@note.Description</td>
}
<td class="col-9 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(note.NoteText, 100)</td>
</tr>
}

View File

@@ -59,7 +59,9 @@
<!option value="FifteenThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.FifteenThousandMiles ? "selected" : "")>15000 mi. / Km</!option>
<!option value="TwentyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.TwentyThousandMiles ? "selected" : "")>20000 mi. / Km</!option>
<!option value="ThirtyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.ThirtyThousandMiles ? "selected" : "")>30000 mi. / Km</!option>
<!option value="FortyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.FortyThousandMiles ? "selected" : "")>40000 mi. / Km</!option>
<!option value="FiftyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.FiftyThousandMiles ? "selected" : "")>50000 mi. / Km</!option>
<!option value="SixtyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.SixtyThousandMiles ? "selected" : "")>60000 mi. / Km</!option>
<!option value="OneHundredThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneHundredThousandMiles ? "selected" : "")>100000 mi. / Km</!option>
<!option value="OneHundredFiftyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneHundredFiftyThousandMiles ? "selected" : "")>150000 mi. / Km</!option>
</select>

View File

@@ -25,6 +25,20 @@
<div class="col-md-6 col-12">
<label for="taxRecordNotes">Notes(optional)</label>
<textarea id="taxRecordNotes" class="form-control" rows="5">@Model.Notes</textarea>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" onChange="enableTaxRecurring()" role="switch" id="taxIsRecurring" checked="@Model.IsRecurring">
<label class="form-check-label" for="taxIsRecurring">Is Recurring</label>
</div>
<label for="taxRecurringMonth">Month</label>
<select class="form-select" id="taxRecurringMonth" @(Model.IsRecurring ? "" : "disabled")>
<!option value="OneMonth" @(Model.RecurringInterval == ReminderMonthInterval.OneMonth ? "selected" : "")>1 Month</!option>
<!option value="ThreeMonths" @(Model.RecurringInterval == ReminderMonthInterval.ThreeMonths || isNew ? "selected" : "")>3 Months</!option>
<!option value="SixMonths" @(Model.RecurringInterval == ReminderMonthInterval.SixMonths ? "selected" : "")>6 Months</!option>
<!option value="OneYear" @(Model.RecurringInterval == ReminderMonthInterval.OneYear ? "selected" : "")>1 Year</!option>
<!option value="TwoYears" @(Model.RecurringInterval == ReminderMonthInterval.TwoYears ? "selected" : "")>2 Years</!option>
<!option value="ThreeYears" @(Model.RecurringInterval == ReminderMonthInterval.ThreeYears ? "selected" : "")>3 Years</!option>
<!option value="FiveYears" @(Model.RecurringInterval == ReminderMonthInterval.FiveYears ? "selected" : "")>5 Years</!option>
</select>
@if (Model.Files.Any())
{
<div>

BIN
docs/dashboard.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

View File

@@ -38,6 +38,8 @@
<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>
<button type="button" data-bs-target="#carouselGallery" data-bs-slide-to="4"></button>
<button type="button" data-bs-target="#carouselGallery" data-bs-slide-to="5"></button>
</div>
<div class="carousel-inner">
<div class="carousel-item active">
@@ -47,6 +49,20 @@
<p>All of your vehicles conveniently displayed in one place</p>
</div>
</div>
<div class="carousel-item">
<img src="dashboard.png" class="d-block w-100" alt="...">
<div class="carousel-caption d-none d-md-block customCarouselCaption">
<h5>Dashboard</h5>
<p>Get an overview of your vehicle expenses</p>
</div>
</div>
<div class="carousel-item">
<img src="planner.png" class="d-block w-100" alt="...">
<div class="carousel-caption d-none d-md-block customCarouselCaption">
<h5>Planner(Kanban Board)</h5>
<p>Plan and track the progress of your To-Do's</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">
@@ -91,6 +107,7 @@
<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">Keeps track of supplies(parts, fluids, 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>
@@ -98,12 +115,13 @@
</div>
<div class="col-12 col-md-6">
<ul class="list-group">
<li class="list-group-item">Keeps track of your To-Do's(Kanban Planner)</li>
<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>
<li class="list-group-item">API Endpoints</li>
<li class="list-group-item">Consolidated Report Export - Just like CarFax</li>
</ul>
</div>
</div>

BIN
docs/planner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

View File

@@ -71,7 +71,7 @@ function saveCollisionRecordToVehicle(isEdit) {
}
function getAndValidateCollisionRecordValues() {
var collisionDate = $("#collisionRecordDate").val();
var collisionMileage = $("#collisionRecordMileage").val();
var collisionMileage = parseInt(globalParseFloat($("#collisionRecordMileage").val())).toString();
var collisionDescription = $("#collisionRecordDescription").val();
var collisionCost = $("#collisionRecordCost").val();
var collisionNotes = $("#collisionRecordNotes").val();

View File

@@ -68,7 +68,7 @@ function saveGasRecordToVehicle(isEdit) {
}
function getAndValidateGasRecordValues() {
var gasDate = $("#gasRecordDate").val();
var gasMileage = $("#gasRecordMileage").val();
var gasMileage = parseInt(globalParseFloat($("#gasRecordMileage").val())).toString();
var gasGallons = $("#gasRecordGallons").val();
var gasCost = $("#gasRecordCost").val();
var gasCostType = $("#gasCostType").val();

View File

@@ -67,6 +67,7 @@ function getAndValidateNoteValues() {
var noteText = $("#noteTextArea").val();
var vehicleId = GetVehicleId().vehicleId;
var noteId = getNoteModelData().id;
var noteIsPinned = $("#noteIsPinned").is(":checked");
//validation
var hasError = false;
if (noteDescription.trim() == '') { //eliminates whitespace.
@@ -86,6 +87,7 @@ function getAndValidateNoteValues() {
hasError: hasError,
vehicleId: vehicleId,
description: noteDescription,
noteText: noteText
noteText: noteText,
pinned: noteIsPinned
}
}

View File

@@ -71,7 +71,7 @@ function saveOdometerRecordToVehicle(isEdit) {
}
function getAndValidateOdometerRecordValues() {
var serviceDate = $("#odometerRecordDate").val();
var serviceMileage = $("#odometerRecordMileage").val();
var serviceMileage = parseInt(globalParseFloat($("#odometerRecordMileage").val())).toString();
var serviceNotes = $("#odometerRecordNotes").val();
var vehicleId = GetVehicleId().vehicleId;
var odometerRecordId = getOdometerRecordModelData().id;

View File

@@ -96,7 +96,7 @@ function markDoneReminderRecord(reminderRecordId, e) {
function getAndValidateReminderRecordValues() {
var reminderDate = $("#reminderDate").val();
var reminderMileage = $("#reminderMileage").val();
var reminderMileage = parseInt(globalParseFloat($("#reminderMileage").val())).toString();
var reminderDescription = $("#reminderDescription").val();
var reminderNotes = $("#reminderNotes").val();
var reminderOption = $('#reminderOptions input:radio:checked').val();

View File

@@ -71,7 +71,7 @@ function saveServiceRecordToVehicle(isEdit) {
}
function getAndValidateServiceRecordValues() {
var serviceDate = $("#serviceRecordDate").val();
var serviceMileage = $("#serviceRecordMileage").val();
var serviceMileage = parseInt(globalParseFloat($("#serviceRecordMileage").val())).toString();
var serviceDescription = $("#serviceRecordDescription").val();
var serviceCost = $("#serviceRecordCost").val();
var serviceNotes = $("#serviceRecordNotes").val();

View File

@@ -18,6 +18,14 @@ function showEditTaxRecordModal(taxRecordId) {
}
});
}
function enableTaxRecurring() {
var taxIsRecurring = $("#taxIsRecurring").is(":checked");
if (taxIsRecurring) {
$("#taxRecurringMonth").attr('disabled', false);
} else {
$("#taxRecurringMonth").attr('disabled', true);
}
}
function hideAddTaxRecordModal() {
$('#taxRecordModal').modal('hide');
}
@@ -76,6 +84,8 @@ function getAndValidateTaxRecordValues() {
var taxNotes = $("#taxRecordNotes").val();
var vehicleId = GetVehicleId().vehicleId;
var taxRecordId = getTaxRecordModelData().id;
var taxIsRecurring = $("#taxIsRecurring").is(":checked");
var taxRecurringMonth = $("#taxRecurringMonth").val();
var addReminderRecord = $("#addReminderCheck").is(":checked");
//validation
var hasError = false;
@@ -105,6 +115,8 @@ function getAndValidateTaxRecordValues() {
description: taxDescription,
cost: taxCost,
notes: taxNotes,
isRecurring: taxIsRecurring,
recurringInterval: taxRecurringMonth,
files: uploadedFiles,
addReminderRecord: addReminderRecord
}

View File

@@ -71,7 +71,7 @@ function saveUpgradeRecordToVehicle(isEdit) {
}
function getAndValidateUpgradeRecordValues() {
var upgradeDate = $("#upgradeRecordDate").val();
var upgradeMileage = $("#upgradeRecordMileage").val();
var upgradeMileage = parseInt(globalParseFloat($("#upgradeRecordMileage").val())).toString();
var upgradeDescription = $("#upgradeRecordDescription").val();
var upgradeCost = $("#upgradeRecordCost").val();
var upgradeNotes = $("#upgradeRecordNotes").val();