Merge pull request #683 from hargata/Hargata/kiosk

Added kiosk view.
This commit is contained in:
Hargata Softworks 2024-10-31 23:51:21 -06:00 committed by GitHub
commit aea870974b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 288 additions and 66 deletions

View File

@ -125,55 +125,7 @@ namespace CarCareTracker.Controllers
vehicles.AddRange(result);
}
List<VehicleInfo> apiResult = new List<VehicleInfo>();
foreach(Vehicle vehicle in vehicles)
{
var currentMileage = _vehicleLogic.GetMaxMileage(vehicle.Id);
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicle.Id);
var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now);
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicle.Id);
var repairRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicle.Id);
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicle.Id);
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicle.Id);
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicle.Id);
var planRecords = _planRecordDataAccess.GetPlanRecordsByVehicleId(vehicle.Id);
var resultToAdd = new VehicleInfo()
{
VehicleData = vehicle,
LastReportedOdometer = currentMileage,
ServiceRecordCount = serviceRecords.Count(),
ServiceRecordCost = serviceRecords.Sum(x=>x.Cost),
RepairRecordCount = repairRecords.Count(),
RepairRecordCost = repairRecords.Sum(x=>x.Cost),
UpgradeRecordCount = upgradeRecords.Count(),
UpgradeRecordCost = upgradeRecords.Sum(x=>x.Cost),
GasRecordCount = gasRecords.Count(),
GasRecordCost = gasRecords.Sum(x=>x.Cost),
TaxRecordCount = taxRecords.Count(),
TaxRecordCost = taxRecords.Sum(x=> x.Cost),
VeryUrgentReminderCount = results.Count(x => x.Urgency == ReminderUrgency.VeryUrgent),
PastDueReminderCount = results.Count(x => x.Urgency == ReminderUrgency.PastDue),
UrgentReminderCount = results.Count(x => x.Urgency == ReminderUrgency.Urgent),
NotUrgentReminderCount = results.Count(x => x.Urgency == ReminderUrgency.NotUrgent),
PlanRecordBackLogCount = planRecords.Count(x=>x.Progress == PlanProgress.Backlog),
PlanRecordInProgressCount = planRecords.Count(x=>x.Progress == PlanProgress.InProgress),
PlanRecordTestingCount = planRecords.Count(x=>x.Progress == PlanProgress.Testing),
PlanRecordDoneCount = planRecords.Count(x=>x.Progress == PlanProgress.Done)
};
//set next reminder
if (results.Any(x => (x.Metric == ReminderMetric.Date || x.Metric == ReminderMetric.Both) && x.Date >= DateTime.Now.Date))
{
resultToAdd.NextReminder = results.Where(x => x.Date >= DateTime.Now.Date).OrderBy(x => x.Date).Select(x => new ReminderExportModel { Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString() }).First();
}
else if (results.Any(x => (x.Metric == ReminderMetric.Odometer || x.Metric == ReminderMetric.Both) && x.Mileage >= currentMileage))
{
resultToAdd.NextReminder = results.Where(x => x.Mileage >= currentMileage).OrderBy(x => x.Mileage).Select(x => new ReminderExportModel { Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString() }).First();
}
apiResult.Add(resultToAdd);
}
var apiResult = _vehicleLogic.GetVehicleInfo(vehicles);
return Json(apiResult);
}
[TypeFilter(typeof(CollaboratorFilter))]
@ -241,7 +193,8 @@ namespace CarCareTracker.Controllers
Description = input.Description,
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Cost = decimal.Parse(input.Cost),
ExtraFields = input.ExtraFields
ExtraFields = input.ExtraFields,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
};
_serviceRecordDataAccess.SaveServiceRecordToVehicle(serviceRecord);
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
@ -318,7 +271,8 @@ namespace CarCareTracker.Controllers
Description = input.Description,
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Cost = decimal.Parse(input.Cost),
ExtraFields = input.ExtraFields
ExtraFields = input.ExtraFields,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
};
_collisionRecordDataAccess.SaveCollisionRecordToVehicle(repairRecord);
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
@ -395,7 +349,8 @@ namespace CarCareTracker.Controllers
Description = input.Description,
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Cost = decimal.Parse(input.Cost),
ExtraFields = input.ExtraFields
ExtraFields = input.ExtraFields,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
};
_upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(upgradeRecord);
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
@ -469,7 +424,8 @@ namespace CarCareTracker.Controllers
Description = input.Description,
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Cost = decimal.Parse(input.Cost),
ExtraFields = input.ExtraFields
ExtraFields = input.ExtraFields,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
};
_taxRecordDataAccess.SaveTaxRecordToVehicle(taxRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Tax Record via API - Description: {taxRecord.Description}");
@ -553,7 +509,8 @@ namespace CarCareTracker.Controllers
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
InitialMileage = (string.IsNullOrWhiteSpace(input.InitialOdometer) || int.Parse(input.InitialOdometer) == default) ? _odometerLogic.GetLastOdometerRecordMileage(vehicleId, new List<OdometerRecord>()) : int.Parse(input.InitialOdometer),
Mileage = int.Parse(input.Odometer),
ExtraFields = input.ExtraFields
ExtraFields = input.ExtraFields,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
};
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Odometer Record via API - Mileage: {odometerRecord.Mileage.ToString()}");
@ -634,7 +591,8 @@ namespace CarCareTracker.Controllers
MissedFuelUp = bool.Parse(input.MissedFuelUp),
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Cost = decimal.Parse(input.Cost),
ExtraFields = input.ExtraFields
ExtraFields = input.ExtraFields,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
};
_gasRecordDataAccess.SaveGasRecordToVehicle(gasRecord);
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)

View File

@ -55,6 +55,28 @@ namespace CarCareTracker.Controllers
{
return View(model: tab);
}
public IActionResult Kiosk(string exceptions)
{
try {
var exceptionList = string.IsNullOrWhiteSpace(exceptions) ? new List<int>() : exceptions.Split(',').Select(x => int.Parse(x)).ToList();
return View(exceptionList);
}
catch (Exception ex)
{
return View(new List<int>());
}
}
public IActionResult KioskContent(List<int> exceptionList)
{
var vehiclesStored = _dataAccess.GetVehicles();
if (!User.IsInRole(nameof(UserData.IsRootUser)))
{
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
}
vehiclesStored.RemoveAll(x => exceptionList.Contains(x.Id));
var result = _vehicleLogic.GetVehicleInfo(vehiclesStored);
return PartialView("_Kiosk", result);
}
public IActionResult Garage()
{
var vehiclesStored = _dataAccess.GetVehicles();
@ -184,10 +206,6 @@ namespace CarCareTracker.Controllers
var result = _config.SaveUserConfig(User, existingConfig);
return Json(result);
}
public IActionResult Privacy()
{
return View();
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
public IActionResult GetExtraFieldsModal(int importMode = 0)
{

View File

@ -32,6 +32,20 @@ namespace CarCareTracker.Helper
return input.ToString();
}
}
public static string GetTitleCaseReminderUrgency(string input)
{
switch (input)
{
case "NotUrgent":
return "Not Urgent";
case "VeryUrgent":
return "Very Urgent";
case "PastDue":
return "Past Due";
default:
return input;
}
}
public static string TruncateStrings(string input, int maxLength = 25)
{

View File

@ -14,6 +14,7 @@ namespace CarCareTracker.Logic
int GetMinMileage(VehicleRecords vehicleRecords);
int GetOwnershipDays(string purchaseDate, string soldDate, List<ServiceRecord> serviceRecords, List<CollisionRecord> repairRecords, List<GasRecord> gasRecords, List<UpgradeRecord> upgradeRecords, List<OdometerRecord> odometerRecords, List<TaxRecord> taxRecords);
bool GetVehicleHasUrgentOrPastDueReminders(int vehicleId, int currentMileage);
List<VehicleInfo> GetVehicleInfo(List<Vehicle> vehicles);
}
public class VehicleLogic: IVehicleLogic
{
@ -24,6 +25,7 @@ namespace CarCareTracker.Logic
private readonly ITaxRecordDataAccess _taxRecordDataAccess;
private readonly IOdometerRecordDataAccess _odometerRecordDataAccess;
private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
private readonly IPlanRecordDataAccess _planRecordDataAccess;
private readonly IReminderHelper _reminderHelper;
public VehicleLogic(
IServiceRecordDataAccess serviceRecordDataAccess,
@ -33,6 +35,7 @@ namespace CarCareTracker.Logic
ITaxRecordDataAccess taxRecordDataAccess,
IOdometerRecordDataAccess odometerRecordDataAccess,
IReminderRecordDataAccess reminderRecordDataAccess,
IPlanRecordDataAccess planRecordDataAccess,
IReminderHelper reminderHelper
) {
_serviceRecordDataAccess = serviceRecordDataAccess;
@ -41,6 +44,7 @@ namespace CarCareTracker.Logic
_upgradeRecordDataAccess = upgradeRecordDataAccess;
_taxRecordDataAccess = taxRecordDataAccess;
_odometerRecordDataAccess = odometerRecordDataAccess;
_planRecordDataAccess = planRecordDataAccess;
_reminderRecordDataAccess = reminderRecordDataAccess;
_reminderHelper = reminderHelper;
}
@ -218,5 +222,59 @@ namespace CarCareTracker.Logic
var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now);
return results.Any(x => x.Urgency == ReminderUrgency.VeryUrgent || x.Urgency == ReminderUrgency.PastDue);
}
public List<VehicleInfo> GetVehicleInfo(List<Vehicle> vehicles)
{
List<VehicleInfo> apiResult = new List<VehicleInfo>();
foreach (Vehicle vehicle in vehicles)
{
var currentMileage = GetMaxMileage(vehicle.Id);
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicle.Id);
var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now);
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicle.Id);
var repairRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicle.Id);
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicle.Id);
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicle.Id);
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicle.Id);
var planRecords = _planRecordDataAccess.GetPlanRecordsByVehicleId(vehicle.Id);
var resultToAdd = new VehicleInfo()
{
VehicleData = vehicle,
LastReportedOdometer = currentMileage,
ServiceRecordCount = serviceRecords.Count(),
ServiceRecordCost = serviceRecords.Sum(x => x.Cost),
RepairRecordCount = repairRecords.Count(),
RepairRecordCost = repairRecords.Sum(x => x.Cost),
UpgradeRecordCount = upgradeRecords.Count(),
UpgradeRecordCost = upgradeRecords.Sum(x => x.Cost),
GasRecordCount = gasRecords.Count(),
GasRecordCost = gasRecords.Sum(x => x.Cost),
TaxRecordCount = taxRecords.Count(),
TaxRecordCost = taxRecords.Sum(x => x.Cost),
VeryUrgentReminderCount = results.Count(x => x.Urgency == ReminderUrgency.VeryUrgent),
PastDueReminderCount = results.Count(x => x.Urgency == ReminderUrgency.PastDue),
UrgentReminderCount = results.Count(x => x.Urgency == ReminderUrgency.Urgent),
NotUrgentReminderCount = results.Count(x => x.Urgency == ReminderUrgency.NotUrgent),
PlanRecordBackLogCount = planRecords.Count(x => x.Progress == PlanProgress.Backlog),
PlanRecordInProgressCount = planRecords.Count(x => x.Progress == PlanProgress.InProgress),
PlanRecordTestingCount = planRecords.Count(x => x.Progress == PlanProgress.Testing),
PlanRecordDoneCount = planRecords.Count(x => x.Progress == PlanProgress.Done)
};
//set next reminder
if (results.Any(x => (x.Metric == ReminderMetric.Date || x.Metric == ReminderMetric.Both) && x.Date >= DateTime.Now.Date))
{
resultToAdd.NextReminder = results.Where(x => x.Date >= DateTime.Now.Date).OrderBy(x => x.Date).Select(x => new ReminderExportModel { Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString() }).First();
}
else if (results.Any(x => (x.Metric == ReminderMetric.Odometer || x.Metric == ReminderMetric.Both) && x.Mileage >= currentMileage))
{
resultToAdd.NextReminder = results.Where(x => x.Mileage >= currentMileage).OrderBy(x => x.Mileage).Select(x => new ReminderExportModel { Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString() }).First();
}
apiResult.Add(resultToAdd);
}
return apiResult;
}
}
}

62
Views/Home/Kiosk.cshtml Normal file
View File

@ -0,0 +1,62 @@
@{
ViewData["Title"] = "Kiosk";
}
@model List<int>
<div class="progress" role="progressbar" aria-label="Refresh Progress" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100" style="height: 1px">
<div class="progress-bar" style="width: 0%"></div>
</div>
<div id="kioskContainer" class="container-fluid">
</div>
<script>
let refreshTimer;
let exceptionList = [];
let subtractAmount = 0;
@foreach(int exception in Model)
{
@:exceptionList.push(@exception);
}
function initKiosk() {
$("body > div").removeClass("container");
$("body > div").css('height', '100vh');
subtractAmount = parseInt($("#kioskContainer").width() * 0.0016); //remove 0.0016% of width every 100 ms which will approximate to one minute.
if (subtractAmount < 2) {
subtractAmount = 2;
}
retrieveKioskContent();
}
function retrieveKioskContent(){
clearInterval(refreshTimer);
$.post('/Home/KioskContent', {exceptionList: exceptionList}, function (data) {
$("#kioskContainer").html(data);
$(".progress-bar").width($("#kioskContainer").width());
setTimeout(function () { startTimer() }, 500);
});
}
function startTimer() {
refreshTimer = setInterval(function () {
var currentWidth = $(".progress-bar").width();
if (currentWidth > 0) {
$(".progress-bar").width(currentWidth - subtractAmount);
} else {
retrieveKioskContent();
}
}, 100);
}
function addVehicleToExceptionList(vehicleId) {
Swal.fire({
title: "Remove Vehicle from Dashboard?",
text: "Removed vehicles can be restored by refreshing the page",
showCancelButton: true,
confirmButtonText: "Remove",
confirmButtonColor: "#dc3545"
}).then((result) => {
if (result.isConfirmed) {
exceptionList.push(vehicleId);
retrieveKioskContent();
}
});
}
initKiosk();
</script>

View File

@ -1,6 +0,0 @@
@{
ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>
<p>Use this page to detail your site's privacy policy.</p>

118
Views/Home/_Kiosk.cshtml Normal file
View File

@ -0,0 +1,118 @@
@using CarCareTracker.Helper
@model List<VehicleInfo>
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="row row-cols-1 row-cols-md-3 g-4 mt-1">
@foreach(VehicleInfo vehicle in Model)
{
@if (!(userConfig.HideSoldVehicles && !string.IsNullOrWhiteSpace(vehicle.VehicleData.SoldDate)))
{
<div class="col">
<div class="card" onclick="addVehicleToExceptionList(@vehicle.VehicleData.Id)">
<div class="card-body" style="padding-top:0.25rem; padding-bottom:0.25rem;">
<h5 class="card-title">@($"{vehicle.VehicleData.Year} {vehicle.VehicleData.Make} {vehicle.VehicleData.Model} ({StaticHelper.GetVehicleIdentifier(vehicle.VehicleData)})")</h5>
<div class="row">
<div class="col-3 text-center">
<p class="display-7">@vehicle.ServiceRecordCount</p>
<p class="lead text-truncate">@translator.Translate(userLanguage, "Service")</p>
<p class="lead text-truncate">@vehicle.ServiceRecordCost.ToString("C0")</p>
</div>
<div class="col-3 text-center">
<p class="display-7">@vehicle.RepairRecordCount</p>
<p class="lead text-truncate">@translator.Translate(userLanguage, "Repairs")</p>
<p class="lead text-truncate">@vehicle.RepairRecordCost.ToString("C0")</p>
</div>
<div class="col-3 text-center">
<p class="display-7">@vehicle.UpgradeRecordCount</p>
<p class="lead text-truncate">@translator.Translate(userLanguage, "Upgrades")</p>
<p class="lead text-truncate">@vehicle.UpgradeRecordCost.ToString("C0")</p>
</div>
<div class="col-3 text-center">
<p class="display-7">@vehicle.GasRecordCount</p>
<p class="lead text-truncate">@translator.Translate(userLanguage, "Fuel")</p>
<p class="lead text-truncate">@vehicle.GasRecordCost.ToString("C0")</p>
</div>
</div>
</div>
@if (vehicle.PastDueReminderCount + vehicle.VeryUrgentReminderCount + vehicle.UrgentReminderCount + vehicle.NotUrgentReminderCount > 0)
{
<hr style="margin:0px;" />
<div class="card-body" style="padding-top:0.25rem; padding-bottom:0.25rem;">
<h5 class="card-title">@translator.Translate(userLanguage, "Reminders")</h5>
<div class="row">
<div class="col-3 text-center">
<p class="display-7">@vehicle.PastDueReminderCount</p>
<p class="lead text-wrap">@translator.Translate(userLanguage, "Past Due")</p>
</div>
<div class="col-3 text-center">
<p class="display-7">@vehicle.VeryUrgentReminderCount</p>
<p class="lead text-wrap">@translator.Translate(userLanguage, "Very Urgent")</p>
</div>
<div class="col-3 text-center">
<p class="display-7">@vehicle.UrgentReminderCount</p>
<p class="lead text-wrap">@translator.Translate(userLanguage, "Urgent")</p>
</div>
<div class="col-3 text-center">
<p class="display-7">@vehicle.NotUrgentReminderCount</p>
<p class="lead text-wrap">@translator.Translate(userLanguage, "Not Urgent")</p>
</div>
</div>
</div>
}
@if (vehicle.NextReminder != null)
{
<hr style="margin:0px;"/>
<div class="card-body" style="padding-top:0.25rem; padding-bottom:0.25rem;">
<h5 class="card-title">@translator.Translate(userLanguage, "Upcoming Reminder")</h5>
<div class="row">
<div class="col-12">
<p class="display-7">@vehicle.NextReminder.Description</p>
<p class="lead text-wrap">@translator.Translate(userLanguage, StaticHelper.GetTitleCaseReminderUrgency(vehicle.NextReminder.Urgency))</p>
<div class="row">
@if (vehicle.NextReminder.Metric == "Date" || vehicle.NextReminder.Metric == "Both")
{
<div class="col-6"><i class='bi bi-calendar-event me-2'></i>@vehicle.NextReminder.DueDate</div>
}
@if (vehicle.NextReminder.Metric == "Odometer" || vehicle.NextReminder.Metric == "Both")
{
<div class="col-6"><i class='bi bi-speedometer me-2'></i>@vehicle.NextReminder.DueOdometer</div>
}
</div>
</div>
</div>
</div>
}
@if (vehicle.PlanRecordBackLogCount + vehicle.PlanRecordInProgressCount + vehicle.PlanRecordTestingCount + vehicle.PlanRecordBackLogCount > 0)
{
<hr style="margin:0px;" />
<div class="card-body" style="padding-top:0.25rem; padding-bottom:0.25rem;">
<h5 class="card-title">@translator.Translate(userLanguage, "Plans")</h5>
<div class="row">
<div class="col-3 text-center">
<p class="display-7">@vehicle.PlanRecordBackLogCount</p>
<p class="lead text-wrap">@translator.Translate(userLanguage, "Planned")</p>
</div>
<div class="col-3 text-center">
<p class="display-7">@vehicle.PlanRecordInProgressCount</p>
<p class="lead text-wrap">@translator.Translate(userLanguage, "Doing")</p>
</div>
<div class="col-3 text-center">
<p class="display-7">@vehicle.PlanRecordTestingCount</p>
<p class="lead text-wrap">@translator.Translate(userLanguage, "Testing")</p>
</div>
<div class="col-3 text-center">
<p class="display-7">@vehicle.PlanRecordDoneCount</p>
<p class="lead text-wrap">@translator.Translate(userLanguage, "Done")</p>
</div>
</div>
</div>
}
</div>
</div>
}
}
</div>

File diff suppressed because one or more lines are too long