bump version and kiosk enhancement.

This commit is contained in:
DESKTOP-T0O5CDB\DESK-555BD
2024-11-01 16:59:10 -06:00
parent f3914bc3b7
commit b06ad0cdaa
14 changed files with 397 additions and 35 deletions

View File

@@ -55,25 +55,56 @@ namespace CarCareTracker.Controllers
{
return View(model: tab);
}
public IActionResult Kiosk(string exceptions)
[Route("/kiosk")]
public IActionResult Kiosk(string exclusions, KioskMode kioskMode = KioskMode.Vehicle)
{
try {
var exceptionList = string.IsNullOrWhiteSpace(exceptions) ? new List<int>() : exceptions.Split(',').Select(x => int.Parse(x)).ToList();
return View(exceptionList);
var viewModel = new KioskViewModel
{
Exclusions = string.IsNullOrWhiteSpace(exclusions) ? new List<int>() : exclusions.Split(',').Select(x => int.Parse(x)).ToList(),
KioskMode = kioskMode
};
return View(viewModel);
}
catch (Exception ex)
{
return View(new List<int>());
_logger.LogError(ex.Message);
return View(new KioskViewModel());
}
}
public IActionResult KioskContent(List<int> exceptionList)
[HttpPost]
public IActionResult KioskContent(KioskViewModel kioskParameters)
{
var vehiclesStored = _dataAccess.GetVehicles();
if (!User.IsInRole(nameof(UserData.IsRootUser)))
{
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
}
vehiclesStored.RemoveAll(x => exceptionList.Contains(x.Id));
vehiclesStored.RemoveAll(x => kioskParameters.Exclusions.Contains(x.Id));
var userConfig = _config.GetUserConfig(User);
if (userConfig.HideSoldVehicles)
{
vehiclesStored.RemoveAll(x => !string.IsNullOrWhiteSpace(x.SoldDate));
}
switch (kioskParameters.KioskMode)
{
case KioskMode.Vehicle:
{
var kioskResult = _vehicleLogic.GetVehicleInfo(vehiclesStored);
return PartialView("_Kiosk", kioskResult);
}
case KioskMode.Plan:
{
var kioskResult = _vehicleLogic.GetPlans(vehiclesStored, true);
return PartialView("_KioskPlan", kioskResult);
}
break;
case KioskMode.Reminder:
{
var kioskResult = _vehicleLogic.GetReminders(vehiclesStored, false);
return PartialView("_KioskReminder", kioskResult);
}
}
var result = _vehicleLogic.GetVehicleInfo(vehiclesStored);
return PartialView("_Kiosk", result);
}
@@ -140,19 +171,7 @@ namespace CarCareTracker.Controllers
{
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
}
List<ReminderRecordViewModel> reminders = new List<ReminderRecordViewModel>();
foreach (Vehicle vehicle in vehiclesStored)
{
var vehicleReminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicle.Id);
vehicleReminders.RemoveAll(x => x.Metric == ReminderMetric.Odometer);
//we don't care about mileages so we can basically fake the current vehicle mileage.
if (vehicleReminders.Any())
{
var reminderUrgency = _reminderHelper.GetReminderRecordViewModels(vehicleReminders, 0, DateTime.Now);
reminderUrgency = reminderUrgency.Select(x => new ReminderRecordViewModel { Id = x.Id, Date = x.Date, Urgency = x.Urgency, Description = $"{vehicle.Year} {vehicle.Make} {vehicle.Model} #{StaticHelper.GetVehicleIdentifier(vehicle)} - {x.Description}" }).ToList();
reminders.AddRange(reminderUrgency);
}
}
var reminders = _vehicleLogic.GetReminders(vehiclesStored, true);
return PartialView("_Calendar", reminders);
}
public IActionResult ViewCalendarReminder(int reminderId)

10
Enum/KioskMode.cs Normal file
View File

@@ -0,0 +1,10 @@
namespace CarCareTracker.Models
{
public enum KioskMode
{
Vehicle = 0,
Plan = 1,
Reminder = 2,
Cycle = 3
}
}

View File

@@ -46,6 +46,52 @@ namespace CarCareTracker.Helper
return input;
}
}
public static string GetReminderUrgencyColor(ReminderUrgency input)
{
switch (input)
{
case ReminderUrgency.NotUrgent:
return "text-bg-success";
case ReminderUrgency.VeryUrgent:
return "text-bg-danger";
case ReminderUrgency.PastDue:
return "text-bg-secondary";
default:
return "text-bg-warning";
}
}
public static string GetPlanRecordColor(PlanPriority input)
{
switch (input)
{
case PlanPriority.Critical:
return "text-bg-danger";
case PlanPriority.Normal:
return "text-bg-primary";
case PlanPriority.Low:
return "text-bg-info";
default:
return "text-bg-primary";
}
}
public static string GetPlanRecordProgress(PlanProgress input)
{
switch (input)
{
case PlanProgress.Backlog:
return "Planned";
case PlanProgress.InProgress:
return "Doing";
case PlanProgress.Testing:
return "Testing";
case PlanProgress.Done:
return "Done";
default:
return input.ToString();
}
}
public static string TruncateStrings(string input, int maxLength = 25)
{

View File

@@ -15,6 +15,8 @@ namespace CarCareTracker.Logic
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);
List<ReminderRecordViewModel> GetReminders(List<Vehicle> vehicles, bool isCalendar);
List<PlanRecord> GetPlans(List<Vehicle> vehicles, bool excludeDone);
}
public class VehicleLogic: IVehicleLogic
{
@@ -276,5 +278,44 @@ namespace CarCareTracker.Logic
}
return apiResult;
}
public List<ReminderRecordViewModel> GetReminders(List<Vehicle> vehicles, bool isCalendar)
{
List<ReminderRecordViewModel> reminders = new List<ReminderRecordViewModel>();
foreach (Vehicle vehicle in vehicles)
{
var vehicleReminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicle.Id);
if (isCalendar)
{
vehicleReminders.RemoveAll(x => x.Metric == ReminderMetric.Odometer);
//we don't care about mileages so we can basically fake the current vehicle mileage.
}
if (vehicleReminders.Any())
{
var vehicleMileage = isCalendar ? 0 : GetMaxMileage(vehicle.Id);
var reminderUrgency = _reminderHelper.GetReminderRecordViewModels(vehicleReminders, vehicleMileage, DateTime.Now);
reminderUrgency = reminderUrgency.Select(x => new ReminderRecordViewModel { Id = x.Id, Metric = x.Metric, Date = x.Date, Notes = x.Notes, Mileage = x.Mileage, Urgency = x.Urgency, Description = $"{vehicle.Year} {vehicle.Make} {vehicle.Model} #{StaticHelper.GetVehicleIdentifier(vehicle)} - {x.Description}" }).ToList();
reminders.AddRange(reminderUrgency);
}
}
return reminders.OrderByDescending(x=>x.Urgency).ToList();
}
public List<PlanRecord> GetPlans(List<Vehicle> vehicles, bool excludeDone)
{
List<PlanRecord> plans = new List<PlanRecord>();
foreach (Vehicle vehicle in vehicles)
{
var vehiclePlans = _planRecordDataAccess.GetPlanRecordsByVehicleId(vehicle.Id);
if (excludeDone)
{
vehiclePlans.RemoveAll(x => x.Progress == PlanProgress.Done);
}
if (vehiclePlans.Any())
{
var convertedPlans = vehiclePlans.Select(x => new PlanRecord { Priority = x.Priority, Progress = x.Progress, Notes = x.Notes, RequisitionHistory = x.RequisitionHistory, Description = $"{vehicle.Year} {vehicle.Make} {vehicle.Model} #{StaticHelper.GetVehicleIdentifier(vehicle)} - {x.Description}" });
plans.AddRange(convertedPlans);
}
}
return plans.OrderBy(x => x.Priority).ThenBy(x=>x.Progress).ToList();
}
}
}

View File

@@ -0,0 +1,14 @@
namespace CarCareTracker.Models
{
public class KioskViewModel
{
/// <summary>
/// List of vehicle ids to exclude from Kiosk Dashboard
/// </summary>
public List<int> Exclusions { get; set; } = new List<int>();
/// <summary>
/// Whether to retrieve data for vehicle, plans, or reminder view.
/// </summary>
public KioskMode KioskMode { get; set; } = KioskMode.Vehicle;
}
}

View File

@@ -42,6 +42,7 @@ Read this [Getting Started Guide](https://docs.lubelogger.com/Installation/Getti
- [Chart.js](https://github.com/chartjs/Chart.js)
- [Drawdown](https://github.com/adamvleggett/drawdown)
- [MailKit](https://github.com/jstedfast/MailKit)
- [Masonry](https://github.com/desandro/masonry)
## License
MIT

View File

@@ -116,6 +116,7 @@
initialOdometer - Initial Odometer reading(optional)<br />
odometer - Odometer reading<br />
notes - notes(optional)<br />
tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
}
</div>
@@ -153,6 +154,7 @@
description - Description<br/>
cost - Cost<br />
notes - notes(optional)<br />
tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
}
</div>
@@ -190,6 +192,7 @@
description - Description<br />
cost - Cost<br />
notes - notes(optional)<br />
tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
}
</div>
@@ -227,6 +230,7 @@
description - Description<br />
cost - Cost<br />
notes - notes(optional)<br />
tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
}
</div>
@@ -263,6 +267,7 @@
description - Description<br />
cost - Cost<br />
notes - notes(optional)<br />
tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
}
</div>
@@ -306,6 +311,7 @@
isFillToFull(bool) - Filled To Full<br />
missedFuelUp(bool) - Missed Fuel Up<br />
notes - notes(optional)<br />
tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
}
</div>

View File

@@ -1,7 +1,10 @@
@{
ViewData["Title"] = "Kiosk";
}
@model List<int>
@model KioskViewModel
@section Scripts {
<script src="~/lib/masonry/masonry.min.js"></script>
}
<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>
@@ -11,8 +14,11 @@
let refreshTimer;
let exceptionList = [];
let subtractAmount = 0;
let kioskMode = '@Model.KioskMode';
let currentKioskMode = 'Plan';
let kioskWakeLock;
@foreach(int exception in Model)
@foreach(int exception in Model.Exclusions)
{
@:exceptionList.push(@exception);
}
@@ -25,14 +31,56 @@
subtractAmount = 2;
}
retrieveKioskContent();
//acquire wakeLock;
try {
navigator.wakeLock.request('screen').then((wl) => {
kioskWakeLock = wl;
});
} catch (err) {
errorToast('Location Services not Enabled');
}
}
function retrieveKioskContent(){
clearInterval(refreshTimer);
$.post('/Home/KioskContent', {exceptionList: exceptionList}, function (data) {
$("#kioskContainer").html(data);
$(".progress-bar").width($("#kioskContainer").width());
setTimeout(function () { startTimer() }, 500);
});
if (kioskMode != 'Cycle'){
$.post('/Home/KioskContent', { exclusions: exceptionList, kioskMode: kioskMode }, function (data) {
$("#kioskContainer").html(data);
$(".kiosk-content").masonry();
if ($(".no-data-message").length == 0) {
$(".progress-bar").width($("#kioskContainer").width());
setTimeout(function () { startTimer() }, 500);
}
});
} else {
//cycle mode
switch (currentKioskMode) {
case "Vehicle":
currentKioskMode = "Reminder";
break;
case "Reminder":
currentKioskMode = "Plan";
break;
case "Plan":
currentKioskMode = "Vehicle";
break;
}
$.post('/Home/KioskContent', { exclusions: exceptionList, kioskMode: currentKioskMode }, function (data) {
$("#kioskContainer").html(data);
$(".kiosk-content").masonry();
if ($(".no-data-message").length > 0) {
//if no data on vehicle page
if (currentKioskMode == "Vehicle") {
return; //exit
} else {
retrieveKioskContent(); //skip until we hit a page with content.
}
} else {
$(".progress-bar").width($("#kioskContainer").width());
setTimeout(function () { startTimer() }, 500);
}
});
}
}
function startTimer() {
refreshTimer = setInterval(function () {
@@ -45,6 +93,9 @@
}, 100);
}
function addVehicleToExceptionList(vehicleId) {
if (kioskMode == 'Cycle') {
return;
}
Swal.fire({
title: "Remove Vehicle from Dashboard?",
text: "Removed vehicles can be restored by refreshing the page",
@@ -58,5 +109,28 @@
}
});
}
function toggleReminderNote(sender){
var reminderNote = $(sender).find('.reminder-note');
if (reminderNote.text().trim() != ''){
if (reminderNote.hasClass('d-none')) {
reminderNote.removeClass('d-none');
} else {
reminderNote.addClass('d-none');
}
$(".kiosk-content").masonry();
}
}
function togglePlanDetails(sender) {
toggleReminderNote(sender);
var planSupplies = $(sender).find('.plan-supplies');
if (planSupplies.find('.plan-supply').length > 0) {
if (planSupplies.hasClass('d-none')) {
planSupplies.removeClass('d-none');
} else {
planSupplies.addClass('d-none');
}
$(".kiosk-content").masonry();
}
}
initKiosk();
</script>

View File

@@ -6,10 +6,10 @@
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)))
@if (Model.Any())
{
<div class="row row-cols-1 row-cols-md-3 g-4 mt-1 kiosk-content" data-masonry='{"percentPosition": true }'>
@foreach (VehicleInfo vehicle in Model)
{
<div class="col">
<div class="card" onclick="addVehicleToExceptionList(@vehicle.VehicleData.Id)">
@@ -65,7 +65,7 @@
}
@if (vehicle.NextReminder != null)
{
<hr style="margin:0px;"/>
<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">
@@ -112,7 +112,15 @@
</div>
}
</div>
</div>
</div>
}
}
</div>
</div>
}
else
{
<div class="row no-data-message">
<div class="col">
<span class="display-3">@translator.Translate(userLanguage, "No records available to display")</span>
</div>
</div>
}

View File

@@ -0,0 +1,86 @@
@using CarCareTracker.Helper
@model List<PlanRecord>
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
@if (Model.Any())
{
<div class="row row-cols-1 row-cols-md-3 g-4 mt-1 kiosk-content" data-masonry='{"percentPosition": true }'>
@foreach (PlanRecord plan in Model)
{
<div class="col" onclick="togglePlanDetails(this)">
<div class="card @StaticHelper.GetPlanRecordColor(plan.Priority)">
<div class="card-body" style="padding-top:0.25rem; padding-bottom:0.25rem;">
<h5 class="card-title">@plan.Description</h5>
<div class="row">
<div class="col-12">
<p class="display-7 d-none reminder-note" style="white-space: pre-wrap">@plan.Notes</p>
<p class="lead text-wrap">@translator.Translate(userLanguage, StaticHelper.GetPlanRecordProgress(plan.Progress))</p>
<div class="row">
<div class="col-6">
@if (plan.ImportMode == ImportMode.ServiceRecord)
{
<span class="lead">@translator.Translate(userLanguage, "Service")</span>
}
else if (plan.ImportMode == ImportMode.UpgradeRecord)
{
<span class="lead">@translator.Translate(userLanguage, "Repairs")</span>
}
else if (plan.ImportMode == ImportMode.RepairRecord)
{
<span class="lead">@translator.Translate(userLanguage, "Upgrades")</span>
}
</div>
</div>
</div>
</div>
</div>
@if (plan.RequisitionHistory.Any())
{
<ul class="list-group list-group-flush plan-supplies d-none">
<li class="list-group-item">
<div class="row">
<div class="col-4">
@translator.Translate(userLanguage, "Part Number")
</div>
<div class="col-4">
@translator.Translate(userLanguage, "Description")
</div>
<div class="col-4">
@translator.Translate(userLanguage, "Quantity")
</div>
</div>
</li>
@foreach (SupplyUsageHistory supply in plan.RequisitionHistory)
{
<li class="list-group-item plan-supply">
<div class="row">
<div class="col-4">
@supply.PartNumber
</div>
<div class="col-4">
@supply.Description
</div>
<div class="col-4">
@supply.Quantity
</div>
</div>
</li>
}
</ul>
}
</div>
</div>
}
</div>
} else
{
<div class="row no-data-message">
<div class="col">
<span class="display-3">@translator.Translate(userLanguage, "No records available to display")</span>
</div>
</div>
}

View File

@@ -0,0 +1,47 @@
@using CarCareTracker.Helper
@model List<ReminderRecordViewModel>
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
@if (Model.Any())
{
<div class="row row-cols-1 row-cols-md-3 g-4 mt-1 kiosk-content" data-masonry='{"percentPosition": true }'>
@foreach (ReminderRecordViewModel reminder in Model)
{
<div class="col" onclick="toggleReminderNote(this)">
<div class="card @StaticHelper.GetReminderUrgencyColor(reminder.Urgency)">
<div class="card-body" style="padding-top:0.25rem; padding-bottom:0.25rem;">
<h5 class="card-title">@reminder.Description</h5>
<div class="row">
<div class="col-12">
<p class="display-7 d-none reminder-note" style="white-space: pre-wrap">@reminder.Notes</p>
<p class="lead text-wrap">@translator.Translate(userLanguage, StaticHelper.GetTitleCaseReminderUrgency(reminder.Urgency))</p>
<div class="row">
@if (reminder.Metric == ReminderMetric.Date || reminder.Metric == ReminderMetric.Both)
{
<div class="col-6"><i class='bi bi-calendar-event me-2'></i>@reminder.Date.ToShortDateString()</div>
}
@if (reminder.Metric == ReminderMetric.Odometer || reminder.Metric == ReminderMetric.Both)
{
<div class="col-6"><i class='bi bi-speedometer me-2'></i>@reminder.Mileage</div>
}
</div>
</div>
</div>
</div>
</div>
</div>
}
</div>
}
else
{
<div class="row no-data-message">
<div class="col">
<span class="display-3">@translator.Translate(userLanguage, "No records available to display")</span>
</div>
</div>
}

View File

@@ -308,6 +308,7 @@
<li class="list-group-item">Chart.js</li>
<li class="list-group-item">Drawdown</li>
<li class="list-group-item">MailKit</li>
<li class="list-group-item">Masonry</li>
</ul>
</div>
</div>

File diff suppressed because one or more lines are too long

9
wwwroot/lib/masonry/masonry.min.js vendored Normal file

File diff suppressed because one or more lines are too long