Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
331499461a | ||
|
|
8c6dd5e343 | ||
|
|
01b1e8228d | ||
|
|
43979d6115 | ||
|
|
6a9860e202 | ||
|
|
39338e8028 | ||
|
|
d1d5351a01 | ||
|
|
c9385c7fdd | ||
|
|
afaae89af6 | ||
|
|
b9d799cd49 | ||
|
|
e47c541e08 | ||
|
|
51ff01d2cd | ||
|
|
618399cb09 | ||
|
|
b837a2e528 | ||
|
|
a1e8b8f9cc | ||
|
|
787c5da72a | ||
|
|
260703be8e | ||
|
|
053801b046 | ||
|
|
db9b1970c5 | ||
|
|
b153ef5ea5 | ||
|
|
b54809f399 | ||
|
|
f7f47c54ff | ||
|
|
92564ae527 | ||
|
|
52ada8574d | ||
|
|
013fb67943 | ||
|
|
d86298f502 | ||
|
|
5891b78be0 | ||
|
|
a9e3e44f2c | ||
|
|
0d1c7234e8 | ||
|
|
e3abe5f209 | ||
|
|
b453bfce5b | ||
|
|
147a1b03a7 | ||
|
|
3017db5f86 | ||
|
|
399d0d8058 | ||
|
|
b8c0d4ef67 | ||
|
|
918d086705 | ||
|
|
ac05acf96b | ||
|
|
e7449806c0 | ||
|
|
baab3213b5 | ||
|
|
d68e9e9589 | ||
|
|
dbb139dfad |
@@ -22,10 +22,13 @@ namespace CarCareTracker.Controllers
|
|||||||
private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
|
private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
|
||||||
private readonly IUpgradeRecordDataAccess _upgradeRecordDataAccess;
|
private readonly IUpgradeRecordDataAccess _upgradeRecordDataAccess;
|
||||||
private readonly IOdometerRecordDataAccess _odometerRecordDataAccess;
|
private readonly IOdometerRecordDataAccess _odometerRecordDataAccess;
|
||||||
|
private readonly IUserAccessDataAccess _userAccessDataAccess;
|
||||||
|
private readonly IUserRecordDataAccess _userRecordDataAccess;
|
||||||
private readonly IReminderHelper _reminderHelper;
|
private readonly IReminderHelper _reminderHelper;
|
||||||
private readonly IGasHelper _gasHelper;
|
private readonly IGasHelper _gasHelper;
|
||||||
private readonly IUserLogic _userLogic;
|
private readonly IUserLogic _userLogic;
|
||||||
private readonly IFileHelper _fileHelper;
|
private readonly IFileHelper _fileHelper;
|
||||||
|
private readonly IMailHelper _mailHelper;
|
||||||
public APIController(IVehicleDataAccess dataAccess,
|
public APIController(IVehicleDataAccess dataAccess,
|
||||||
IGasHelper gasHelper,
|
IGasHelper gasHelper,
|
||||||
IReminderHelper reminderHelper,
|
IReminderHelper reminderHelper,
|
||||||
@@ -37,6 +40,9 @@ namespace CarCareTracker.Controllers
|
|||||||
IReminderRecordDataAccess reminderRecordDataAccess,
|
IReminderRecordDataAccess reminderRecordDataAccess,
|
||||||
IUpgradeRecordDataAccess upgradeRecordDataAccess,
|
IUpgradeRecordDataAccess upgradeRecordDataAccess,
|
||||||
IOdometerRecordDataAccess odometerRecordDataAccess,
|
IOdometerRecordDataAccess odometerRecordDataAccess,
|
||||||
|
IUserAccessDataAccess userAccessDataAccess,
|
||||||
|
IUserRecordDataAccess userRecordDataAccess,
|
||||||
|
IMailHelper mailHelper,
|
||||||
IFileHelper fileHelper,
|
IFileHelper fileHelper,
|
||||||
IUserLogic userLogic)
|
IUserLogic userLogic)
|
||||||
{
|
{
|
||||||
@@ -49,6 +55,9 @@ namespace CarCareTracker.Controllers
|
|||||||
_reminderRecordDataAccess = reminderRecordDataAccess;
|
_reminderRecordDataAccess = reminderRecordDataAccess;
|
||||||
_upgradeRecordDataAccess = upgradeRecordDataAccess;
|
_upgradeRecordDataAccess = upgradeRecordDataAccess;
|
||||||
_odometerRecordDataAccess = odometerRecordDataAccess;
|
_odometerRecordDataAccess = odometerRecordDataAccess;
|
||||||
|
_userAccessDataAccess = userAccessDataAccess;
|
||||||
|
_userRecordDataAccess = userRecordDataAccess;
|
||||||
|
_mailHelper = mailHelper;
|
||||||
_gasHelper = gasHelper;
|
_gasHelper = gasHelper;
|
||||||
_reminderHelper = reminderHelper;
|
_reminderHelper = reminderHelper;
|
||||||
_userLogic = userLogic;
|
_userLogic = userLogic;
|
||||||
@@ -428,12 +437,65 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||||
[HttpGet]
|
[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")]
|
[Route("/api/makebackup")]
|
||||||
public IActionResult MakeBackup()
|
public IActionResult MakeBackup()
|
||||||
{
|
{
|
||||||
var result = _fileHelper.MakeBackup();
|
var result = _fileHelper.MakeBackup();
|
||||||
return Json(result);
|
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)
|
private int GetMaxMileage(int vehicleId)
|
||||||
{
|
{
|
||||||
var numbersArray = new List<int>();
|
var numbersArray = new List<int>();
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult Index(int vehicleId)
|
public IActionResult Index(int vehicleId)
|
||||||
{
|
{
|
||||||
var data = _dataAccess.GetVehicleById(vehicleId);
|
var data = _dataAccess.GetVehicleById(vehicleId);
|
||||||
|
UpdateRecurringTaxes(vehicleId);
|
||||||
return View(data);
|
return View(data);
|
||||||
}
|
}
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@@ -316,7 +317,6 @@ namespace CarCareTracker.Controllers
|
|||||||
var vehicleRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
|
var vehicleRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
|
||||||
bool useMPG = _config.GetUserConfig(User).UseMPG;
|
bool useMPG = _config.GetUserConfig(User).UseMPG;
|
||||||
bool useUKMPG = _config.GetUserConfig(User).UseUKMPG;
|
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 convertedRecords = _gasHelper.GetGasRecordViewModels(vehicleRecords, useMPG, useUKMPG);
|
||||||
var exportData = convertedRecords.Select(x => new GasRecordExportModel
|
var exportData = convertedRecords.Select(x => new GasRecordExportModel
|
||||||
{
|
{
|
||||||
@@ -376,7 +376,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
VehicleId = vehicleId,
|
VehicleId = vehicleId,
|
||||||
Date = DateTime.Parse(importModel.Date),
|
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),
|
Gallons = decimal.Parse(importModel.FuelConsumed, NumberStyles.Any),
|
||||||
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes
|
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes
|
||||||
};
|
};
|
||||||
@@ -420,7 +420,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
VehicleId = vehicleId,
|
VehicleId = vehicleId,
|
||||||
Date = DateTime.Parse(importModel.Date),
|
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,
|
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Service Record on {importModel.Date}" : importModel.Description,
|
||||||
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
||||||
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any)
|
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any)
|
||||||
@@ -433,7 +433,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
VehicleId = vehicleId,
|
VehicleId = vehicleId,
|
||||||
Date = DateTime.Parse(importModel.Date),
|
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
|
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes
|
||||||
};
|
};
|
||||||
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(convertedRecord);
|
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(convertedRecord);
|
||||||
@@ -463,7 +463,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
VehicleId = vehicleId,
|
VehicleId = vehicleId,
|
||||||
Date = DateTime.Parse(importModel.Date),
|
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,
|
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Repair Record on {importModel.Date}" : importModel.Description,
|
||||||
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
||||||
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any)
|
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any)
|
||||||
@@ -476,7 +476,7 @@ namespace CarCareTracker.Controllers
|
|||||||
{
|
{
|
||||||
VehicleId = vehicleId,
|
VehicleId = vehicleId,
|
||||||
Date = DateTime.Parse(importModel.Date),
|
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,
|
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Upgrade Record on {importModel.Date}" : importModel.Description,
|
||||||
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
|
||||||
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any)
|
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any)
|
||||||
@@ -529,8 +529,6 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult GetGasRecordsByVehicleId(int vehicleId)
|
public IActionResult GetGasRecordsByVehicleId(int vehicleId)
|
||||||
{
|
{
|
||||||
var result = _gasRecordDataAccess.GetGasRecordsByVehicleId(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.
|
//check if the user uses MPG or Liters per 100km.
|
||||||
var userConfig = _config.GetUserConfig(User);
|
var userConfig = _config.GetUserConfig(User);
|
||||||
bool useMPG = userConfig.UseMPG;
|
bool useMPG = userConfig.UseMPG;
|
||||||
@@ -757,6 +755,34 @@ namespace CarCareTracker.Controllers
|
|||||||
}
|
}
|
||||||
return PartialView("_TaxRecords", result);
|
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]
|
[HttpPost]
|
||||||
public IActionResult SaveTaxRecordToVehicleId(TaxRecordInput taxRecord)
|
public IActionResult SaveTaxRecordToVehicleId(TaxRecordInput taxRecord)
|
||||||
{
|
{
|
||||||
@@ -783,6 +809,8 @@ namespace CarCareTracker.Controllers
|
|||||||
Description = result.Description,
|
Description = result.Description,
|
||||||
Notes = result.Notes,
|
Notes = result.Notes,
|
||||||
VehicleId = result.VehicleId,
|
VehicleId = result.VehicleId,
|
||||||
|
IsRecurring = result.IsRecurring,
|
||||||
|
RecurringInterval = result.RecurringInterval,
|
||||||
Files = result.Files
|
Files = result.Files
|
||||||
};
|
};
|
||||||
return PartialView("_TaxRecordModal", convertedResult);
|
return PartialView("_TaxRecordModal", convertedResult);
|
||||||
@@ -816,7 +844,7 @@ namespace CarCareTracker.Controllers
|
|||||||
UpgradeRecordSum = upgradeRecords.Sum(x => x.Cost)
|
UpgradeRecordSum = upgradeRecords.Sum(x => x.Cost)
|
||||||
};
|
};
|
||||||
//get costbymonth
|
//get costbymonth
|
||||||
List<CostForVehicleByMonth> allCosts = new List<CostForVehicleByMonth>();
|
List<CostForVehicleByMonth> allCosts = StaticHelper.GetBaseLineCosts();
|
||||||
allCosts.AddRange(_reportHelper.GetServiceRecordSum(serviceRecords, 0));
|
allCosts.AddRange(_reportHelper.GetServiceRecordSum(serviceRecords, 0));
|
||||||
allCosts.AddRange(_reportHelper.GetRepairRecordSum(collisionRecords, 0));
|
allCosts.AddRange(_reportHelper.GetRepairRecordSum(collisionRecords, 0));
|
||||||
allCosts.AddRange(_reportHelper.GetUpgradeRecordSum(upgradeRecords, 0));
|
allCosts.AddRange(_reportHelper.GetUpgradeRecordSum(upgradeRecords, 0));
|
||||||
@@ -867,10 +895,17 @@ namespace CarCareTracker.Controllers
|
|||||||
var userConfig = _config.GetUserConfig(User);
|
var userConfig = _config.GetUserConfig(User);
|
||||||
var mileageData = _gasHelper.GetGasRecordViewModels(gasRecords, userConfig.UseMPG, userConfig.UseUKMPG);
|
var mileageData = _gasHelper.GetGasRecordViewModels(gasRecords, userConfig.UseMPG, userConfig.UseUKMPG);
|
||||||
mileageData.RemoveAll(x => x.MilesPerGallon == default);
|
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)
|
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();
|
}).ToList();
|
||||||
viewModel.FuelMileageForVehicleByMonth = monthlyMileageData;
|
viewModel.FuelMileageForVehicleByMonth = monthlyMileageData;
|
||||||
return PartialView("_Report", viewModel);
|
return PartialView("_Report", viewModel);
|
||||||
@@ -956,7 +991,7 @@ namespace CarCareTracker.Controllers
|
|||||||
var gasViewModels = _gasHelper.GetGasRecordViewModels(gasRecords, useMPG, useUKMPG);
|
var gasViewModels = _gasHelper.GetGasRecordViewModels(gasRecords, useMPG, useUKMPG);
|
||||||
if (gasViewModels.Any())
|
if (gasViewModels.Any())
|
||||||
{
|
{
|
||||||
averageMPG = _gasHelper.GetAverageGasMileage(gasViewModels);
|
averageMPG = _gasHelper.GetAverageGasMileage(gasViewModels, useMPG);
|
||||||
}
|
}
|
||||||
vehicleHistory.MPG = averageMPG;
|
vehicleHistory.MPG = averageMPG;
|
||||||
//insert servicerecords
|
//insert servicerecords
|
||||||
@@ -1012,10 +1047,17 @@ namespace CarCareTracker.Controllers
|
|||||||
mileageData.RemoveAll(x => DateTime.Parse(x.Date).Year != year);
|
mileageData.RemoveAll(x => DateTime.Parse(x.Date).Year != year);
|
||||||
}
|
}
|
||||||
mileageData.RemoveAll(x => x.MilesPerGallon == default);
|
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)
|
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();
|
}).ToList();
|
||||||
return PartialView("_MPGByMonthReport", monthlyMileageData);
|
return PartialView("_MPGByMonthReport", monthlyMileageData);
|
||||||
}
|
}
|
||||||
@@ -1023,7 +1065,7 @@ namespace CarCareTracker.Controllers
|
|||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult GetCostByMonthByVehicle(int vehicleId, List<ImportMode> selectedMetrics, int year = 0)
|
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))
|
if (selectedMetrics.Contains(ImportMode.ServiceRecord))
|
||||||
{
|
{
|
||||||
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
|
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
|
||||||
@@ -1266,6 +1308,7 @@ namespace CarCareTracker.Controllers
|
|||||||
public IActionResult GetNotesByVehicleId(int vehicleId)
|
public IActionResult GetNotesByVehicleId(int vehicleId)
|
||||||
{
|
{
|
||||||
var result = _noteDataAccess.GetNotesByVehicleId(vehicleId);
|
var result = _noteDataAccess.GetNotesByVehicleId(vehicleId);
|
||||||
|
result = result.OrderByDescending(x => x.Pinned).ToList();
|
||||||
return PartialView("_Notes", result);
|
return PartialView("_Notes", result);
|
||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
|
|||||||
@@ -14,7 +14,9 @@
|
|||||||
FifteenThousandMiles = 15000,
|
FifteenThousandMiles = 15000,
|
||||||
TwentyThousandMiles = 20000,
|
TwentyThousandMiles = 20000,
|
||||||
ThirtyThousandMiles = 30000,
|
ThirtyThousandMiles = 30000,
|
||||||
|
FortyThousandMiles = 40000,
|
||||||
FiftyThousandMiles = 50000,
|
FiftyThousandMiles = 50000,
|
||||||
|
SixtyThousandMiles = 60000,
|
||||||
OneHundredThousandMiles = 100000,
|
OneHundredThousandMiles = 100000,
|
||||||
OneHundredFiftyThousandMiles = 150000
|
OneHundredFiftyThousandMiles = 150000
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
{
|
{
|
||||||
public enum ReminderMonthInterval
|
public enum ReminderMonthInterval
|
||||||
{
|
{
|
||||||
|
OneMonth = 1,
|
||||||
ThreeMonths = 3,
|
ThreeMonths = 3,
|
||||||
SixMonths = 6,
|
SixMonths = 6,
|
||||||
OneYear = 12,
|
OneYear = 12,
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ namespace CarCareTracker.Helper
|
|||||||
public interface IGasHelper
|
public interface IGasHelper
|
||||||
{
|
{
|
||||||
List<GasRecordViewModel> GetGasRecordViewModels(List<GasRecord> result, bool useMPG, bool useUKMPG);
|
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 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 recordWithCalculatedMPG = results.Where(x => x.MilesPerGallon > 0);
|
||||||
var minMileage = results.Min(x => x.Mileage);
|
var minMileage = results.Min(x => x.Mileage);
|
||||||
@@ -19,12 +19,18 @@ namespace CarCareTracker.Helper
|
|||||||
var totalGallonsConsumed = recordWithCalculatedMPG.Sum(x => x.Gallons);
|
var totalGallonsConsumed = recordWithCalculatedMPG.Sum(x => x.Gallons);
|
||||||
var deltaMileage = maxMileage - minMileage;
|
var deltaMileage = maxMileage - minMileage;
|
||||||
var averageGasMileage = (maxMileage - minMileage) / totalGallonsConsumed;
|
var averageGasMileage = (maxMileage - minMileage) / totalGallonsConsumed;
|
||||||
|
if (!useMPG)
|
||||||
|
{
|
||||||
|
averageGasMileage = 100 / averageGasMileage;
|
||||||
|
}
|
||||||
return averageGasMileage.ToString("F");
|
return averageGasMileage.ToString("F");
|
||||||
}
|
}
|
||||||
return "0";
|
return "0";
|
||||||
}
|
}
|
||||||
public List<GasRecordViewModel> GetGasRecordViewModels(List<GasRecord> result, bool useMPG, bool useUKMPG)
|
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>();
|
var computedResults = new List<GasRecordViewModel>();
|
||||||
int previousMileage = 0;
|
int previousMileage = 0;
|
||||||
decimal unFactoredConsumption = 0.00M;
|
decimal unFactoredConsumption = 0.00M;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ namespace CarCareTracker.Helper
|
|||||||
{
|
{
|
||||||
OperationResponse NotifyUserForRegistration(string emailAddress, string token);
|
OperationResponse NotifyUserForRegistration(string emailAddress, string token);
|
||||||
OperationResponse NotifyUserForPasswordReset(string emailAddress, string token);
|
OperationResponse NotifyUserForPasswordReset(string emailAddress, string token);
|
||||||
|
OperationResponse NotifyUserForReminders(Vehicle vehicle, List<string> emailAddresses, List<ReminderRecordViewModel> reminders);
|
||||||
}
|
}
|
||||||
public class MailHelper : IMailHelper
|
public class MailHelper : IMailHelper
|
||||||
{
|
{
|
||||||
@@ -60,20 +61,62 @@ namespace CarCareTracker.Helper
|
|||||||
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
|
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 to = emailTo;
|
||||||
string from = mailConfig.EmailFrom;
|
string from = mailConfig.EmailFrom;
|
||||||
var server = mailConfig.EmailServer;
|
var server = mailConfig.EmailServer;
|
||||||
MailMessage message = new MailMessage(from, to);
|
MailMessage message = new MailMessage(from, to);
|
||||||
message.Subject = emailSubject;
|
message.Subject = emailSubject;
|
||||||
message.Body = emailBody;
|
message.Body = emailBody;
|
||||||
|
message.IsBodyHtml = isBodyHtml;
|
||||||
SmtpClient client = new SmtpClient(server);
|
SmtpClient client = new SmtpClient(server);
|
||||||
client.EnableSsl = mailConfig.UseSSL;
|
client.EnableSsl = mailConfig.UseSSL;
|
||||||
client.Port = mailConfig.Port;
|
client.Port = mailConfig.Port;
|
||||||
client.Credentials = new NetworkCredential(mailConfig.Username, mailConfig.Password);
|
client.Credentials = new NetworkCredential(mailConfig.Username, mailConfig.Password);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
client.Send(message);
|
if (useAsync)
|
||||||
|
{
|
||||||
|
client.SendMailAsync(message, new CancellationToken());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
client.Send(message);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using CarCareTracker.Models;
|
using CarCareTracker.Models;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
namespace CarCareTracker.Helper
|
namespace CarCareTracker.Helper
|
||||||
{
|
{
|
||||||
@@ -63,5 +64,41 @@ namespace CarCareTracker.Helper
|
|||||||
}
|
}
|
||||||
return "";
|
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}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,5 +6,6 @@
|
|||||||
public int VehicleId { get; set; }
|
public int VehicleId { get; set; }
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public string NoteText { get; set; }
|
public string NoteText { get; set; }
|
||||||
|
public bool Pinned { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public decimal Cost { get; set; }
|
public decimal Cost { get; set; }
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
|
public bool IsRecurring { get; set; } = false;
|
||||||
|
public ReminderMonthInterval RecurringInterval { get; set; } = ReminderMonthInterval.OneYear;
|
||||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,18 @@
|
|||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public decimal Cost { get; set; }
|
public decimal Cost { get; set; }
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
|
public bool IsRecurring { get; set; } = false;
|
||||||
|
public ReminderMonthInterval RecurringInterval { get; set; } = ReminderMonthInterval.ThreeMonths;
|
||||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
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 }; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
README.md
11
README.md
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
A self-hosted, open-source vehicle service records and maintainence tracker.
|
A self-hosted, open-source vehicle service records and maintainence tracker.
|
||||||
|
|
||||||
|
Visit our website: https://lubelogger.com
|
||||||
|
|
||||||
Support this project on Patreon: https://patreon.com/LubeLogger
|
Support this project on Patreon: https://patreon.com/LubeLogger
|
||||||
|
|
||||||
## Why
|
## Why
|
||||||
@@ -10,6 +12,11 @@ Because nobody should have to deal with a homemade spreadsheet or a shoebox full
|
|||||||
## Screenshots
|
## Screenshots
|
||||||
<a href="/docs/screenshots.md">Screenshots</a>
|
<a href="/docs/screenshots.md">Screenshots</a>
|
||||||
|
|
||||||
|
## Demo
|
||||||
|
Try it out before you download it! The live demo resets every 20 minutes.
|
||||||
|
|
||||||
|
[Live Demo](https://demo.lubelogger.com) Login using username "test" and password "1234"
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
- Bootstrap
|
- Bootstrap
|
||||||
- LiteDB
|
- LiteDB
|
||||||
@@ -22,7 +29,7 @@ Because nobody should have to deal with a homemade spreadsheet or a shoebox full
|
|||||||
1. Install Docker
|
1. Install Docker
|
||||||
2. Run `docker pull ghcr.io/hargata/lubelogger:latest`
|
2. Run `docker pull ghcr.io/hargata/lubelogger:latest`
|
||||||
3. CHECK culture in .env file, default is en_US, this will change the currency and date formats. You can also setup SMTP Config here.
|
3. CHECK culture in .env file, default is en_US, this will change the currency and date formats. You can also setup SMTP Config here.
|
||||||
4. If not using traefik, use docker-compose-notraefik.yml
|
4. If using traefik, use docker-compose.traefik.yml
|
||||||
5. Run `docker-compose up`
|
5. Run `docker-compose up`
|
||||||
|
|
||||||
## Docker Setup (Manual Build)
|
## Docker Setup (Manual Build)
|
||||||
@@ -31,7 +38,7 @@ Because nobody should have to deal with a homemade spreadsheet or a shoebox full
|
|||||||
3. CHECK culture in .env file, default is en_US, also setup SMTP for user management if you want that.
|
3. CHECK culture in .env file, default is en_US, also setup SMTP for user management if you want that.
|
||||||
4. Run `docker build -t lubelogger -f Dockerfile .`
|
4. Run `docker build -t lubelogger -f Dockerfile .`
|
||||||
5. CHECK docker-compose.yml and make sure the mounting directories look correct.
|
5. CHECK docker-compose.yml and make sure the mounting directories look correct.
|
||||||
6. If not using traefik, use docker-compose-notraefik.yml
|
6. If using traefik, use docker-compose.traefik.yml
|
||||||
7. Run `docker-compose up`
|
7. Run `docker-compose up`
|
||||||
|
|
||||||
## Additional Docker Instructions
|
## Additional Docker Instructions
|
||||||
|
|||||||
@@ -241,6 +241,21 @@
|
|||||||
</div>
|
</div>
|
||||||
@if (User.IsInRole(nameof(UserData.IsRootUser)))
|
@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="row">
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
GET
|
GET
|
||||||
|
|||||||
@@ -154,7 +154,7 @@
|
|||||||
<img src="/defaults/lubelogger_logo.png" />
|
<img src="/defaults/lubelogger_logo.png" />
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-center">
|
<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>
|
</div>
|
||||||
<p class="lead">
|
<p class="lead">
|
||||||
Proudly developed in the rural town of Price, Utah by Hargata Softworks.
|
Proudly developed in the rural town of Price, Utah by Hargata Softworks.
|
||||||
|
|||||||
@@ -12,14 +12,14 @@
|
|||||||
labels: ["Service Records", "Repairs", "Upgrades", "Tax", "Fuel"],
|
labels: ["Service Records", "Repairs", "Upgrades", "Tax", "Fuel"],
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: "Expenses by Category",
|
label: "Expenses by Type",
|
||||||
backgroundColor: ["#003f5c", "#58508d", "#bc5090", "#ff6361", "#ffa600"],
|
backgroundColor: ["#003f5c", "#58508d", "#bc5090", "#ff6361", "#ffa600"],
|
||||||
data: [
|
data: [
|
||||||
@Model.ServiceRecordSum,
|
globalParseFloat('@Model.ServiceRecordSum'),
|
||||||
@Model.CollisionRecordSum,
|
globalParseFloat('@Model.CollisionRecordSum'),
|
||||||
@Model.UpgradeRecordSum,
|
globalParseFloat('@Model.UpgradeRecordSum'),
|
||||||
@Model.TaxRecordSum,
|
globalParseFloat('@Model.TaxRecordSum'),
|
||||||
@Model.GasRecordSum
|
globalParseFloat('@Model.GasRecordSum')
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
<span class="ms-2 badge bg-success">@($"# of Gas Records: {Model.GasRecords.Count()}")</span>
|
<span class="ms-2 badge bg-success">@($"# of Gas Records: {Model.GasRecords.Count()}")</span>
|
||||||
@if (Model.GasRecords.Where(x => x.MilesPerGallon > 0).Any())
|
@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">@($"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>
|
<span class="ms-2 badge bg-primary">@($"Max Fuel Economy: {Model.GasRecords.Max(x => x.MilesPerGallon).ToString("F") ?? "0"}")</span>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
var barGraphColors = new string[] { "#00876c", "#43956e", "#67a371", "#89b177", "#a9be80", "#c8cb8b", "#e6d79b", "#e4c281", "#e3ab6b", "#e2925b", "#e07952", "#db5d4f" };
|
var barGraphColors = new string[] { "#00876c", "#43956e", "#67a371", "#89b177", "#a9be80", "#c8cb8b", "#e6d79b", "#e4c281", "#e3ab6b", "#e2925b", "#e07952", "#db5d4f" };
|
||||||
var sortedByMPG = Model.OrderBy(x => x.Cost).ToList();
|
var sortedByMPG = Model.OrderBy(x => x.Cost).ToList();
|
||||||
}
|
}
|
||||||
@if (Model.Any())
|
@if (Model.Where(x=>x.Cost > 0).Any())
|
||||||
{
|
{
|
||||||
<canvas id="bar-chart"></canvas>
|
<canvas id="bar-chart"></canvas>
|
||||||
<script>
|
<script>
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
@foreach (CostForVehicleByMonth gasCost in Model)
|
@foreach (CostForVehicleByMonth gasCost in Model)
|
||||||
{
|
{
|
||||||
@:barGraphLabels.push("@gasCost.MonthName");
|
@:barGraphLabels.push("@gasCost.MonthName");
|
||||||
@:barGraphData.push(@gasCost.Cost);
|
@:barGraphData.push(globalParseFloat('@gasCost.Cost'));
|
||||||
var index = sortedByMPG.FindIndex(x => x.MonthName == gasCost.MonthName);
|
var index = sortedByMPG.FindIndex(x => x.MonthName == gasCost.MonthName);
|
||||||
@:barGraphColors.push('@barGraphColors[index]');
|
@:barGraphColors.push('@barGraphColors[index]');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
var barGraphColors = new string[] { "#00876c", "#43956e", "#67a371", "#89b177", "#a9be80", "#c8cb8b", "#e6d79b", "#e4c281", "#e3ab6b", "#e2925b", "#e07952", "#db5d4f" };
|
var barGraphColors = new string[] { "#00876c", "#43956e", "#67a371", "#89b177", "#a9be80", "#c8cb8b", "#e6d79b", "#e4c281", "#e3ab6b", "#e2925b", "#e07952", "#db5d4f" };
|
||||||
var sortedByMPG = Model.OrderByDescending(x => x.Cost).ToList();
|
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>
|
<canvas id="bar-chart-mpg"></canvas>
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
@foreach (CostForVehicleByMonth gasCost in Model)
|
@foreach (CostForVehicleByMonth gasCost in Model)
|
||||||
{
|
{
|
||||||
@:barGraphLabels.push("@gasCost.MonthName");
|
@:barGraphLabels.push("@gasCost.MonthName");
|
||||||
@:barGraphData.push(@gasCost.Cost);
|
@:barGraphData.push(globalParseFloat('@gasCost.Cost'));
|
||||||
var index = sortedByMPG.FindIndex(x => x.MonthName == gasCost.MonthName);
|
var index = sortedByMPG.FindIndex(x => x.MonthName == gasCost.MonthName);
|
||||||
@:barGraphColors.push('@barGraphColors[index]');
|
@:barGraphColors.push('@barGraphColors[index]');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,10 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<input type="text" id="workAroundInput" style="height:0px; width:0px; display:none;">
|
<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>
|
<label for="noteDescription">Description</label>
|
||||||
<input type="text" id="noteDescription" class="form-control" placeholder="Description of the note" value="@(isNew ? "" : Model.Description)">
|
<input type="text" id="noteDescription" class="form-control" placeholder="Description of the note" value="@(isNew ? "" : Model.Description)">
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -27,7 +27,13 @@
|
|||||||
@foreach (Note note in Model)
|
@foreach (Note note in Model)
|
||||||
{
|
{
|
||||||
<tr class="d-flex" style="cursor:pointer;" onclick="showEditNoteModal(@note.Id)">
|
<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>
|
<td class="col-9 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(note.NoteText, 100)</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,9 @@
|
|||||||
<!option value="FifteenThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.FifteenThousandMiles ? "selected" : "")>15000 mi. / Km</!option>
|
<!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="TwentyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.TwentyThousandMiles ? "selected" : "")>20000 mi. / Km</!option>
|
||||||
<!option value="ThirtyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.ThirtyThousandMiles ? "selected" : "")>30000 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="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="OneHundredThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneHundredThousandMiles ? "selected" : "")>100000 mi. / Km</!option>
|
||||||
<!option value="OneHundredFiftyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneHundredFiftyThousandMiles ? "selected" : "")>150000 mi. / Km</!option>
|
<!option value="OneHundredFiftyThousandMiles" @(Model.ReminderMileageInterval == ReminderMileageInterval.OneHundredFiftyThousandMiles ? "selected" : "")>150000 mi. / Km</!option>
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -25,6 +25,20 @@
|
|||||||
<div class="col-md-6 col-12">
|
<div class="col-md-6 col-12">
|
||||||
<label for="taxRecordNotes">Notes(optional)</label>
|
<label for="taxRecordNotes">Notes(optional)</label>
|
||||||
<textarea id="taxRecordNotes" class="form-control" rows="5">@Model.Notes</textarea>
|
<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())
|
@if (Model.Files.Any())
|
||||||
{
|
{
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
BIN
docs/dashboard.png
Normal file
BIN
docs/dashboard.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
21
docs/documentation/Collaboration.md
Normal file
21
docs/documentation/Collaboration.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Sharing your Vehicles and Collaborating with Other Users
|
||||||
|
|
||||||
|
LubeLogger allows you to collaborate on vehicles so that more than one user can add or edit records on a vehicle.
|
||||||
|
|
||||||
|
To share a vehicle, simply navigate into the Vehicle's Dashboard, and look to the bottom left
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Click on the little blue button with a User Add icon and you will be prompted to enter the user's username
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Note:** The username is case sensitive and the user must exist in the system.
|
||||||
|
|
||||||
|
Once you have added the user, their username will then show up in the list of Collaborators
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Now the user can view and edit the vehicle as well:
|
||||||
|
|
||||||
|

|
||||||
51
docs/documentation/Configuring.md
Normal file
51
docs/documentation/Configuring.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Configuring LubeLogger
|
||||||
|
In order to provide the best possible user experience, we have provided ample amount of flexibility when it comes to user settings.
|
||||||
|
Upon initial launch, you are using the Root User by default without any authentication, so you will have access to all of the settings.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Most of the settings are relatively straightforward and self-explanatory.
|
||||||
|
|
||||||
|
**Note:** If you are a user in the UK and you wish be able to input Fuel Purchases in Liters but display Fuel Mileage as Miles Per UK Gallons, you will need to enable "Use Imperial Calculation" and "Use UK MPG Calculation"
|
||||||
|
|
||||||
|
**Note:** When making changes to Settings as a root user, your settings will be saved and served up as the default server settings for any new users that sign up.
|
||||||
|
|
||||||
|
## Enable Authentication
|
||||||
|
It is highly recommended that you secure your LubeLogger instance by enabling authentication.
|
||||||
|
To do so, simply check "Enable Authentication" and you will be prompted to enter a Username and Password
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The credentials that you set up here are the credentials for the Root User, aka the Super Admin, and shouldn't be shared with anyone else.
|
||||||
|
|
||||||
|
Once you have entered the credentials, you will then be redirected to a Login page
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Simply enter the credentials you have just set up and you will be logged right in
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Setting Up Multiple Users
|
||||||
|
To set up multiple users, all you have to do is click on the dropdown that has your username on it and select "Admin Panel"
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
If you have SMTP configured correctly, the "Auto Notify(via Email) switch will be enabled and checked, otherwise it will be disabled/grayed out.
|
||||||
|
Without SMTP Configured:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
With SMTP Configured:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
To generate a new user token, simply click on the "Generate User Token" button and you will be prompted with the user's email address
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
If you have SMTP Configured, the user will then receive an Email that looks similar to this:
|
||||||
|

|
||||||
|
|
||||||
|
The user can then proceed to Register for an account at your instance of LubeLogger.
|
||||||
|
|
||||||
75
docs/documentation/GettingStarted.md
Normal file
75
docs/documentation/GettingStarted.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# Getting Started
|
||||||
|
## Docker
|
||||||
|
The Docker Container Repository is the most reliable and up-to-date distribution channel for LubeLogger.
|
||||||
|
You need to have Docker Windows installed and Virtualization enabled(typically a BIOS setting).
|
||||||
|
|
||||||
|
You will then clone the following files onto your computer from the repository _.env_ and _docker-compose.yml_ or _docker-compose-traefik.yml_ if you're using Traefik.
|
||||||
|
|
||||||
|
In the .env file you will find the following and here are the explanations for the variables.
|
||||||
|
```
|
||||||
|
LC_ALL=en_US.UTF-8 <- Locale and Language Settings, this will affect how numbers, currencies, and dates are formatted.
|
||||||
|
LANG=en_US.UTF-8 <- Same as above. Note that some languages don't have UTF-8 encodings.
|
||||||
|
MailConfig__EmailServer="" <- Email SMTP settings used only for configuring multiple users(to send their registration token and forgot password tokens)
|
||||||
|
MailConfig__EmailFrom="" <- Same as above.
|
||||||
|
MailConfig__UseSSL="false" <- Same as above.
|
||||||
|
MailConfig__Port=587 <- Same as above.
|
||||||
|
MailConfig__Username="" <- Same as above.
|
||||||
|
MailConfig__Password="" <- Same as above.
|
||||||
|
```
|
||||||
|
|
||||||
|
Once you're happy with the configuration, run the following commands to pull down the image and run container.
|
||||||
|
```
|
||||||
|
docker pull ghcr.io/hargata/lubelogger:latest
|
||||||
|
docker-compose up
|
||||||
|
```
|
||||||
|
By default the app will start listening at localhost:8080, this port can be configured in the docker-compose file.
|
||||||
|
|
||||||
|
## Windows Standalone Executable
|
||||||
|
Windows Standalone executables are provided on a request basis, and will usually be included with every other release.
|
||||||
|
|
||||||
|
To run the server, you just have to double click on CarCareTracker.exe
|
||||||
|
|
||||||
|
Occassionally you might run into an issue regarding a missing folder, to fix that, just create a "config" folder where CarCareTracker.exe is located.
|
||||||
|
|
||||||
|
If you wish to set up SMTP when using this approach, you will have to configure the environment settings in appsettings.json located in the same folder as CarCareTracker.exe
|
||||||
|
You just have to add the MailConfig section into it, but I provided the full appsettings.json anyways as an example.
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"UseDarkMode": false,
|
||||||
|
"EnableCsvImports": true,
|
||||||
|
"UseMPG": true,
|
||||||
|
"UseDescending": false,
|
||||||
|
"EnableAuth": false,
|
||||||
|
"HideZero": false,
|
||||||
|
"EnableAutoReminderRefresh": false,
|
||||||
|
"EnableAutoOdometerInsert": false,
|
||||||
|
"UseUKMPG": false,
|
||||||
|
"UseThreeDecimalGasCost": true,
|
||||||
|
"VisibleTabs": [ 0, 1, 4, 2, 3, 6, 5, 8 ],
|
||||||
|
"DefaultTab": 8,
|
||||||
|
"UserNameHash": "",
|
||||||
|
"UserPasswordHash": "",
|
||||||
|
"MailConfig": {
|
||||||
|
"EmailServer": "",
|
||||||
|
"EmailFrom": "",
|
||||||
|
"UseSSL": true,
|
||||||
|
"Port": 587,
|
||||||
|
"Username": "",
|
||||||
|
"Password": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
When using this approach, the default port the app will be listening on is 5000, so you will navigate to localhost:5000
|
||||||
|
|
||||||
|
## Test that It Works
|
||||||
|
Whichever path you choose, once you get the app up and running, just navigate to the IP address and port the server is listening to and you should be able to see the app
|
||||||
|

|
||||||
|

|
||||||
22
docs/documentation/Registration.md
Normal file
22
docs/documentation/Registration.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Registration
|
||||||
|
Once they user have receive an email containing their LubeLogger token(pictured below), they can then register for an account.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
They can do so by navigating to your LubeLogger instance and clicking on the "Register" link.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Then they just have to provide the token and their email address along with their set of credentials
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Note:** The email address and token are CASE SENSITIVE and MUST be identical to the email address that the token is generated for.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Once the user has registered successfully, they can then log in using their credentials.
|
||||||
|
|
||||||
|
You can also verify that in your panel the token is deleted and the user now shows up under list of users:
|
||||||
|
|
||||||
|

|
||||||
@@ -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="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="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="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>
|
||||||
<div class="carousel-inner">
|
<div class="carousel-inner">
|
||||||
<div class="carousel-item active">
|
<div class="carousel-item active">
|
||||||
@@ -47,6 +49,20 @@
|
|||||||
<p>All of your vehicles conveniently displayed in one place</p>
|
<p>All of your vehicles conveniently displayed in one place</p>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="carousel-item">
|
||||||
<img src="servicerecord.png" class="d-block w-100" alt="...">
|
<img src="servicerecord.png" class="d-block w-100" alt="...">
|
||||||
<div class="carousel-caption d-none d-md-block customCarouselCaption">
|
<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 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 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 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">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">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>
|
<li class="list-group-item">Attach documents for each record(receipts, invoices, etc)</li>
|
||||||
@@ -98,16 +115,32 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-6">
|
||||||
<ul class="list-group">
|
<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">Set reminders so you never miss another scheduled maintenance</li>
|
||||||
<li class="list-group-item">Dark Mode</li>
|
<li class="list-group-item">Dark Mode</li>
|
||||||
<li class="list-group-item">Mobile/Small screen support</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">Basic Authentication for security</li>
|
||||||
<li class="list-group-item">Coming Soon(API Endpoints)</li>
|
<li class="list-group-item">API Endpoints</li>
|
||||||
<li class="list-group-item">Coming Soon(Consolidated Report Export - Just like CarFax)</li>
|
<li class="list-group-item">Consolidated Report Export - Just like CarFax</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 d-flex justify-content-center">
|
||||||
|
<h6 class="display-6 text-center">Try It Out</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 d-flex justify-content-center">
|
||||||
|
<p class="lead">
|
||||||
|
Live demo available <a href="https://demo.lubelogger.com">here</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 d-flex justify-content-center">
|
||||||
|
<p class="lead">Login to the demo using the username "test" and password "1234". The demo site resets every 20 minutes.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 d-flex justify-content-center">
|
<div class="col-12 d-flex justify-content-center">
|
||||||
<h6 class="display-6 text-center">Where to Download</h6>
|
<h6 class="display-6 text-center">Where to Download</h6>
|
||||||
@@ -152,7 +185,14 @@
|
|||||||
<hr>
|
<hr>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 d-flex justify-content-center">
|
<div class="col-12 d-flex justify-content-center">
|
||||||
<p class="lead">LubeLogger is proudly developed in Price Utah by Hargata Softworks
|
<h6 class="display-6 text-center">About</h6>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 d-flex justify-content-center">
|
||||||
|
<p class="lead">LubeLogger is proudly developed in Price Utah by Hargata Softworks.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 d-flex justify-content-center">
|
||||||
|
<p class="lead"><a href="https://www.patreon.com/LubeLogger">Support us on Patreon</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 d-flex justify-content-center">
|
<div class="col-12 d-flex justify-content-center">
|
||||||
|
|||||||
BIN
docs/planner.png
Normal file
BIN
docs/planner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
BIN
wwwroot/defaults/demo_default.zip
Normal file
BIN
wwwroot/defaults/demo_default.zip
Normal file
Binary file not shown.
@@ -71,7 +71,7 @@ function saveCollisionRecordToVehicle(isEdit) {
|
|||||||
}
|
}
|
||||||
function getAndValidateCollisionRecordValues() {
|
function getAndValidateCollisionRecordValues() {
|
||||||
var collisionDate = $("#collisionRecordDate").val();
|
var collisionDate = $("#collisionRecordDate").val();
|
||||||
var collisionMileage = $("#collisionRecordMileage").val();
|
var collisionMileage = parseInt(globalParseFloat($("#collisionRecordMileage").val())).toString();
|
||||||
var collisionDescription = $("#collisionRecordDescription").val();
|
var collisionDescription = $("#collisionRecordDescription").val();
|
||||||
var collisionCost = $("#collisionRecordCost").val();
|
var collisionCost = $("#collisionRecordCost").val();
|
||||||
var collisionNotes = $("#collisionRecordNotes").val();
|
var collisionNotes = $("#collisionRecordNotes").val();
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ function saveGasRecordToVehicle(isEdit) {
|
|||||||
}
|
}
|
||||||
function getAndValidateGasRecordValues() {
|
function getAndValidateGasRecordValues() {
|
||||||
var gasDate = $("#gasRecordDate").val();
|
var gasDate = $("#gasRecordDate").val();
|
||||||
var gasMileage = $("#gasRecordMileage").val();
|
var gasMileage = parseInt(globalParseFloat($("#gasRecordMileage").val())).toString();
|
||||||
var gasGallons = $("#gasRecordGallons").val();
|
var gasGallons = $("#gasRecordGallons").val();
|
||||||
var gasCost = $("#gasRecordCost").val();
|
var gasCost = $("#gasRecordCost").val();
|
||||||
var gasCostType = $("#gasCostType").val();
|
var gasCostType = $("#gasCostType").val();
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ function getAndValidateNoteValues() {
|
|||||||
var noteText = $("#noteTextArea").val();
|
var noteText = $("#noteTextArea").val();
|
||||||
var vehicleId = GetVehicleId().vehicleId;
|
var vehicleId = GetVehicleId().vehicleId;
|
||||||
var noteId = getNoteModelData().id;
|
var noteId = getNoteModelData().id;
|
||||||
|
var noteIsPinned = $("#noteIsPinned").is(":checked");
|
||||||
//validation
|
//validation
|
||||||
var hasError = false;
|
var hasError = false;
|
||||||
if (noteDescription.trim() == '') { //eliminates whitespace.
|
if (noteDescription.trim() == '') { //eliminates whitespace.
|
||||||
@@ -86,6 +87,7 @@ function getAndValidateNoteValues() {
|
|||||||
hasError: hasError,
|
hasError: hasError,
|
||||||
vehicleId: vehicleId,
|
vehicleId: vehicleId,
|
||||||
description: noteDescription,
|
description: noteDescription,
|
||||||
noteText: noteText
|
noteText: noteText,
|
||||||
|
pinned: noteIsPinned
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,7 @@ function saveOdometerRecordToVehicle(isEdit) {
|
|||||||
}
|
}
|
||||||
function getAndValidateOdometerRecordValues() {
|
function getAndValidateOdometerRecordValues() {
|
||||||
var serviceDate = $("#odometerRecordDate").val();
|
var serviceDate = $("#odometerRecordDate").val();
|
||||||
var serviceMileage = $("#odometerRecordMileage").val();
|
var serviceMileage = parseInt(globalParseFloat($("#odometerRecordMileage").val())).toString();
|
||||||
var serviceNotes = $("#odometerRecordNotes").val();
|
var serviceNotes = $("#odometerRecordNotes").val();
|
||||||
var vehicleId = GetVehicleId().vehicleId;
|
var vehicleId = GetVehicleId().vehicleId;
|
||||||
var odometerRecordId = getOdometerRecordModelData().id;
|
var odometerRecordId = getOdometerRecordModelData().id;
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ function markDoneReminderRecord(reminderRecordId, e) {
|
|||||||
|
|
||||||
function getAndValidateReminderRecordValues() {
|
function getAndValidateReminderRecordValues() {
|
||||||
var reminderDate = $("#reminderDate").val();
|
var reminderDate = $("#reminderDate").val();
|
||||||
var reminderMileage = $("#reminderMileage").val();
|
var reminderMileage = parseInt(globalParseFloat($("#reminderMileage").val())).toString();
|
||||||
var reminderDescription = $("#reminderDescription").val();
|
var reminderDescription = $("#reminderDescription").val();
|
||||||
var reminderNotes = $("#reminderNotes").val();
|
var reminderNotes = $("#reminderNotes").val();
|
||||||
var reminderOption = $('#reminderOptions input:radio:checked').val();
|
var reminderOption = $('#reminderOptions input:radio:checked').val();
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ function saveServiceRecordToVehicle(isEdit) {
|
|||||||
}
|
}
|
||||||
function getAndValidateServiceRecordValues() {
|
function getAndValidateServiceRecordValues() {
|
||||||
var serviceDate = $("#serviceRecordDate").val();
|
var serviceDate = $("#serviceRecordDate").val();
|
||||||
var serviceMileage = $("#serviceRecordMileage").val();
|
var serviceMileage = parseInt(globalParseFloat($("#serviceRecordMileage").val())).toString();
|
||||||
var serviceDescription = $("#serviceRecordDescription").val();
|
var serviceDescription = $("#serviceRecordDescription").val();
|
||||||
var serviceCost = $("#serviceRecordCost").val();
|
var serviceCost = $("#serviceRecordCost").val();
|
||||||
var serviceNotes = $("#serviceRecordNotes").val();
|
var serviceNotes = $("#serviceRecordNotes").val();
|
||||||
|
|||||||
@@ -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() {
|
function hideAddTaxRecordModal() {
|
||||||
$('#taxRecordModal').modal('hide');
|
$('#taxRecordModal').modal('hide');
|
||||||
}
|
}
|
||||||
@@ -76,6 +84,8 @@ function getAndValidateTaxRecordValues() {
|
|||||||
var taxNotes = $("#taxRecordNotes").val();
|
var taxNotes = $("#taxRecordNotes").val();
|
||||||
var vehicleId = GetVehicleId().vehicleId;
|
var vehicleId = GetVehicleId().vehicleId;
|
||||||
var taxRecordId = getTaxRecordModelData().id;
|
var taxRecordId = getTaxRecordModelData().id;
|
||||||
|
var taxIsRecurring = $("#taxIsRecurring").is(":checked");
|
||||||
|
var taxRecurringMonth = $("#taxRecurringMonth").val();
|
||||||
var addReminderRecord = $("#addReminderCheck").is(":checked");
|
var addReminderRecord = $("#addReminderCheck").is(":checked");
|
||||||
//validation
|
//validation
|
||||||
var hasError = false;
|
var hasError = false;
|
||||||
@@ -105,6 +115,8 @@ function getAndValidateTaxRecordValues() {
|
|||||||
description: taxDescription,
|
description: taxDescription,
|
||||||
cost: taxCost,
|
cost: taxCost,
|
||||||
notes: taxNotes,
|
notes: taxNotes,
|
||||||
|
isRecurring: taxIsRecurring,
|
||||||
|
recurringInterval: taxRecurringMonth,
|
||||||
files: uploadedFiles,
|
files: uploadedFiles,
|
||||||
addReminderRecord: addReminderRecord
|
addReminderRecord: addReminderRecord
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ function saveUpgradeRecordToVehicle(isEdit) {
|
|||||||
}
|
}
|
||||||
function getAndValidateUpgradeRecordValues() {
|
function getAndValidateUpgradeRecordValues() {
|
||||||
var upgradeDate = $("#upgradeRecordDate").val();
|
var upgradeDate = $("#upgradeRecordDate").val();
|
||||||
var upgradeMileage = $("#upgradeRecordMileage").val();
|
var upgradeMileage = parseInt(globalParseFloat($("#upgradeRecordMileage").val())).toString();
|
||||||
var upgradeDescription = $("#upgradeRecordDescription").val();
|
var upgradeDescription = $("#upgradeRecordDescription").val();
|
||||||
var upgradeCost = $("#upgradeRecordCost").val();
|
var upgradeCost = $("#upgradeRecordCost").val();
|
||||||
var upgradeNotes = $("#upgradeRecordNotes").val();
|
var upgradeNotes = $("#upgradeRecordNotes").val();
|
||||||
|
|||||||
Reference in New Issue
Block a user