Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5a5de50a7 | ||
|
|
7715e7b4ab | ||
|
|
5a3ed3d3d4 | ||
|
|
c1e2394194 | ||
|
|
6abb569df1 | ||
|
|
e59e33bc3b | ||
|
|
a671dc1937 | ||
|
|
426b52aee4 | ||
|
|
87fe011565 | ||
|
|
30c03af5b0 | ||
|
|
c9c8fc8705 | ||
|
|
9cd8030808 | ||
|
|
5411d4152c | ||
|
|
c5a05afe1c | ||
|
|
5b44f2ae33 | ||
|
|
039b30da4f | ||
|
|
df45378bce | ||
|
|
3729d2c23f | ||
|
|
89ece413c8 | ||
|
|
c1b361397f | ||
|
|
ada0715343 | ||
|
|
5cdc687f6d | ||
|
|
48f0e16fde | ||
|
|
d6b6600ce9 | ||
|
|
82634e9e1b | ||
|
|
225e5e58bc | ||
|
|
21d135a8ab | ||
|
|
7a0636a48a | ||
|
|
0b9ca77281 | ||
|
|
feaf631b47 | ||
|
|
ec24c19821 | ||
|
|
36a120fa0f | ||
|
|
fb169d5054 | ||
|
|
59fa7b6a1d | ||
|
|
201d832b92 | ||
|
|
31b4ef5458 | ||
|
|
b06ad0cdaa | ||
|
|
f3914bc3b7 | ||
|
|
aea870974b | ||
|
|
7f6265ba05 | ||
|
|
85542cb278 | ||
|
|
f6e0873d83 | ||
|
|
745b262dfc | ||
|
|
73df755f70 | ||
|
|
762a1b98d3 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,3 +12,4 @@ config/userConfig.json
|
||||
CarCareTracker.csproj.user
|
||||
Properties/launchSettings.json
|
||||
data/cartracker-log.db
|
||||
data/widgets.html
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -55,6 +55,59 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
return View(model: tab);
|
||||
}
|
||||
[Route("/kiosk")]
|
||||
public IActionResult Kiosk(string exclusions, KioskMode kioskMode = KioskMode.Vehicle)
|
||||
{
|
||||
try {
|
||||
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)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return View(new KioskViewModel());
|
||||
}
|
||||
}
|
||||
[HttpPost]
|
||||
public IActionResult KioskContent(KioskViewModel kioskParameters)
|
||||
{
|
||||
var vehiclesStored = _dataAccess.GetVehicles();
|
||||
if (!User.IsInRole(nameof(UserData.IsRootUser)))
|
||||
{
|
||||
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
|
||||
}
|
||||
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);
|
||||
}
|
||||
public IActionResult Garage()
|
||||
{
|
||||
var vehiclesStored = _dataAccess.GetVehicles();
|
||||
@@ -118,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)
|
||||
@@ -184,10 +225,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)
|
||||
{
|
||||
@@ -468,6 +505,57 @@ namespace CarCareTracker.Controllers
|
||||
return Json(new OperationResponse() { Success = false, Message = StaticHelper.GenericErrorMessage });
|
||||
}
|
||||
}
|
||||
public ActionResult GetVehicleSelector(int vehicleId)
|
||||
{
|
||||
var vehiclesStored = _dataAccess.GetVehicles();
|
||||
if (!User.IsInRole(nameof(UserData.IsRootUser)))
|
||||
{
|
||||
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
|
||||
}
|
||||
if (vehicleId != default)
|
||||
{
|
||||
vehiclesStored.RemoveAll(x => x.Id == vehicleId);
|
||||
}
|
||||
var userConfig = _config.GetUserConfig(User);
|
||||
if (userConfig.HideSoldVehicles)
|
||||
{
|
||||
vehiclesStored.RemoveAll(x => !string.IsNullOrWhiteSpace(x.SoldDate));
|
||||
}
|
||||
return PartialView("_VehicleSelector", vehiclesStored);
|
||||
}
|
||||
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||
[HttpGet]
|
||||
public IActionResult GetCustomWidgetEditor()
|
||||
{
|
||||
if (_config.GetCustomWidgetsEnabled())
|
||||
{
|
||||
var customWidgetData = _fileHelper.GetWidgets();
|
||||
return PartialView("_WidgetEditor", customWidgetData);
|
||||
}
|
||||
return Json(string.Empty);
|
||||
}
|
||||
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||
[HttpPost]
|
||||
public IActionResult SaveCustomWidgets(string widgetsData)
|
||||
{
|
||||
if (_config.GetCustomWidgetsEnabled())
|
||||
{
|
||||
var saveResult = _fileHelper.SaveWidgets(widgetsData);
|
||||
return Json(saveResult);
|
||||
}
|
||||
return Json(false);
|
||||
}
|
||||
[Authorize(Roles = nameof(UserData.IsRootUser))]
|
||||
[HttpPost]
|
||||
public IActionResult DeleteCustomWidgets()
|
||||
{
|
||||
if (_config.GetCustomWidgetsEnabled())
|
||||
{
|
||||
var deleteResult = _fileHelper.DeleteWidgets();
|
||||
return Json(deleteResult);
|
||||
}
|
||||
return Json(false);
|
||||
}
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public IActionResult Error()
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Npgsql;
|
||||
using System.IO.Compression;
|
||||
using JsonSerializer=System.Text.Json.JsonSerializer;
|
||||
|
||||
namespace CarCareTracker.Controllers
|
||||
{
|
||||
@@ -105,7 +106,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
Vehicle vehicle = System.Text.Json.JsonSerializer.Deserialize<Vehicle>(reader["data"] as string);
|
||||
Vehicle vehicle = JsonSerializer.Deserialize<Vehicle>(reader["data"] as string);
|
||||
vehicles.Add(vehicle);
|
||||
}
|
||||
}
|
||||
@@ -123,7 +124,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
repairrecords.Add(System.Text.Json.JsonSerializer.Deserialize<CollisionRecord>(reader["data"] as string));
|
||||
repairrecords.Add(JsonSerializer.Deserialize<CollisionRecord>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in repairrecords)
|
||||
@@ -140,7 +141,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
upgraderecords.Add(System.Text.Json.JsonSerializer.Deserialize<UpgradeRecord>(reader["data"] as string));
|
||||
upgraderecords.Add(JsonSerializer.Deserialize<UpgradeRecord>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in upgraderecords)
|
||||
@@ -157,7 +158,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
servicerecords.Add(System.Text.Json.JsonSerializer.Deserialize<ServiceRecord>(reader["data"] as string));
|
||||
servicerecords.Add(JsonSerializer.Deserialize<ServiceRecord>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in servicerecords)
|
||||
@@ -176,7 +177,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
gasrecords.Add(System.Text.Json.JsonSerializer.Deserialize<GasRecord>(reader["data"] as string));
|
||||
gasrecords.Add(JsonSerializer.Deserialize<GasRecord>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in gasrecords)
|
||||
@@ -193,7 +194,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
noterecords.Add(System.Text.Json.JsonSerializer.Deserialize<Note>(reader["data"] as string));
|
||||
noterecords.Add(JsonSerializer.Deserialize<Note>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in noterecords)
|
||||
@@ -210,7 +211,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
odometerrecords.Add(System.Text.Json.JsonSerializer.Deserialize<OdometerRecord>(reader["data"] as string));
|
||||
odometerrecords.Add(JsonSerializer.Deserialize<OdometerRecord>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in odometerrecords)
|
||||
@@ -227,7 +228,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
reminderrecords.Add(System.Text.Json.JsonSerializer.Deserialize<ReminderRecord>(reader["data"] as string));
|
||||
reminderrecords.Add(JsonSerializer.Deserialize<ReminderRecord>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in reminderrecords)
|
||||
@@ -246,7 +247,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
planrecords.Add(System.Text.Json.JsonSerializer.Deserialize<PlanRecord>(reader["data"] as string));
|
||||
planrecords.Add(JsonSerializer.Deserialize<PlanRecord>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in planrecords)
|
||||
@@ -263,7 +264,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
planrecordtemplates.Add(System.Text.Json.JsonSerializer.Deserialize<PlanRecordInput>(reader["data"] as string));
|
||||
planrecordtemplates.Add(JsonSerializer.Deserialize<PlanRecordInput>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in planrecordtemplates)
|
||||
@@ -280,7 +281,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
supplyrecords.Add(System.Text.Json.JsonSerializer.Deserialize<SupplyRecord>(reader["data"] as string));
|
||||
supplyrecords.Add(JsonSerializer.Deserialize<SupplyRecord>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in supplyrecords)
|
||||
@@ -297,7 +298,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
taxrecords.Add(System.Text.Json.JsonSerializer.Deserialize<TaxRecord>(reader["data"] as string));
|
||||
taxrecords.Add(JsonSerializer.Deserialize<TaxRecord>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in taxrecords)
|
||||
@@ -360,7 +361,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
userconfigrecords.Add(System.Text.Json.JsonSerializer.Deserialize<UserConfigData>(reader["data"] as string));
|
||||
userconfigrecords.Add(JsonSerializer.Deserialize<UserConfigData>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in userconfigrecords)
|
||||
@@ -404,7 +405,7 @@ namespace CarCareTracker.Controllers
|
||||
using (NpgsqlDataReader reader = ctext.ExecuteReader())
|
||||
while (reader.Read())
|
||||
{
|
||||
extrafields.Add(System.Text.Json.JsonSerializer.Deserialize<RecordExtraField>(reader["data"] as string));
|
||||
extrafields.Add(JsonSerializer.Deserialize<RecordExtraField>(reader["data"] as string));
|
||||
}
|
||||
}
|
||||
foreach (var record in extrafields)
|
||||
@@ -475,7 +476,7 @@ namespace CarCareTracker.Controllers
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", vehicle.Id);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(vehicle));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(vehicle));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
@@ -491,7 +492,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
@@ -507,7 +508,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
@@ -523,7 +524,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
@@ -541,7 +542,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
@@ -557,7 +558,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
@@ -573,7 +574,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
@@ -589,7 +590,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
@@ -607,7 +608,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
@@ -623,7 +624,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
@@ -639,7 +640,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
@@ -655,7 +656,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
@@ -706,7 +707,7 @@ namespace CarCareTracker.Controllers
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
@@ -738,7 +739,7 @@ namespace CarCareTracker.Controllers
|
||||
using (var ctext = pgDataSource.CreateCommand(cmd))
|
||||
{
|
||||
ctext.Parameters.AddWithValue("id", record.Id);
|
||||
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
|
||||
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
|
||||
ctext.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using CarCareTracker.Helper;
|
||||
using CarCareTracker.MapProfile;
|
||||
using CarCareTracker.Models;
|
||||
using CsvHelper;
|
||||
using CsvHelper.Configuration;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Globalization;
|
||||
|
||||
@@ -259,7 +260,7 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
using (var reader = new StreamReader(fullFileName))
|
||||
{
|
||||
var config = new CsvHelper.Configuration.CsvConfiguration(CultureInfo.InvariantCulture);
|
||||
var config = new CsvConfiguration(CultureInfo.InvariantCulture);
|
||||
config.MissingFieldFound = null;
|
||||
config.HeaderValidated = null;
|
||||
config.PrepareHeaderForMatch = args => { return args.Header.Trim().ToLower(); };
|
||||
|
||||
@@ -66,6 +66,24 @@ namespace CarCareTracker.Controllers
|
||||
var result = _planRecordTemplateDataAccess.DeletePlanRecordTemplateById(planRecordTemplateId);
|
||||
return Json(result);
|
||||
}
|
||||
[HttpGet]
|
||||
public IActionResult OrderPlanSupplies(int planRecordTemplateId)
|
||||
{
|
||||
var existingRecord = _planRecordTemplateDataAccess.GetPlanRecordTemplateById(planRecordTemplateId);
|
||||
if (existingRecord.Id == default)
|
||||
{
|
||||
return Json(new OperationResponse { Success = false, Message = "Unable to find template" });
|
||||
}
|
||||
if (existingRecord.Supplies.Any())
|
||||
{
|
||||
var suppliesToOrder = CheckSupplyRecordsAvailability(existingRecord.Supplies);
|
||||
return PartialView("_PlanOrderSupplies", suppliesToOrder);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Json(new OperationResponse { Success = false, Message = "Template has No Supplies" });
|
||||
}
|
||||
}
|
||||
[HttpPost]
|
||||
public IActionResult ConvertPlanRecordTemplateToPlanRecord(int planRecordTemplateId)
|
||||
{
|
||||
@@ -78,9 +96,13 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
//check if all supplies are available
|
||||
var supplyAvailability = CheckSupplyRecordsAvailability(existingRecord.Supplies);
|
||||
if (supplyAvailability.Any())
|
||||
if (supplyAvailability.Any(x => x.Missing))
|
||||
{
|
||||
return Json(new OperationResponse { Success = false, Message = string.Join("<br>", supplyAvailability) });
|
||||
return Json(new OperationResponse { Success = false, Message = "Missing Supplies, Please Delete This Template and Recreate It." });
|
||||
}
|
||||
else if (supplyAvailability.Any(x => x.Insufficient))
|
||||
{
|
||||
return Json(new OperationResponse { Success = false, Message = "Insufficient Supplies" });
|
||||
}
|
||||
}
|
||||
if (existingRecord.ReminderRecordId != default)
|
||||
|
||||
@@ -22,6 +22,8 @@ namespace CarCareTracker.Controllers
|
||||
var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
|
||||
var userConfig = _config.GetUserConfig(User);
|
||||
var viewModel = new ReportViewModel();
|
||||
//check if custom widgets are configured
|
||||
viewModel.CustomWidgetsConfigured = _fileHelper.WidgetsExist();
|
||||
//get totalCostMakeUp
|
||||
viewModel.CostMakeUpForVehicle = new CostMakeUpForVehicle
|
||||
{
|
||||
@@ -105,7 +107,7 @@ namespace CarCareTracker.Controllers
|
||||
}).ToList();
|
||||
if (invertedFuelMileageUnit)
|
||||
{
|
||||
foreach(CostForVehicleByMonth monthMileage in monthlyMileageData)
|
||||
foreach (CostForVehicleByMonth monthMileage in monthlyMileageData)
|
||||
{
|
||||
if (monthMileage.Cost != default)
|
||||
{
|
||||
@@ -113,7 +115,7 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
}
|
||||
}
|
||||
var mpgViewModel = new MPGForVehicleByMonth {
|
||||
var mpgViewModel = new MPGForVehicleByMonth {
|
||||
CostData = monthlyMileageData,
|
||||
Unit = invertedFuelMileageUnit ? preferredFuelMileageUnit : fuelEconomyMileageUnit,
|
||||
SortedCostData = (userConfig.UseMPG || invertedFuelMileageUnit) ? monthlyMileageData.OrderByDescending(x => x.Cost).ToList() : monthlyMileageData.OrderBy(x => x.Cost).ToList()
|
||||
@@ -318,10 +320,34 @@ namespace CarCareTracker.Controllers
|
||||
return Json(new OperationResponse { Success = false, Message = "No Attachments Found" });
|
||||
}
|
||||
}
|
||||
public IActionResult GetReportParameters()
|
||||
{
|
||||
var viewModel = new ReportParameter() {
|
||||
VisibleColumns = new List<string> {
|
||||
nameof(GenericReportModel.DataType),
|
||||
nameof(GenericReportModel.Date),
|
||||
nameof(GenericReportModel.Odometer),
|
||||
nameof(GenericReportModel.Description),
|
||||
nameof(GenericReportModel.Cost),
|
||||
nameof(GenericReportModel.Notes)
|
||||
}
|
||||
};
|
||||
//get all extra fields from service records, repairs, upgrades, and tax records.
|
||||
var recordTypes = new List<int>() { 0, 1, 3, 4 };
|
||||
var extraFields = new List<string>();
|
||||
foreach(int recordType in recordTypes)
|
||||
{
|
||||
extraFields.AddRange(_extraFieldDataAccess.GetExtraFieldsById(recordType).ExtraFields.Select(x => x.Name));
|
||||
}
|
||||
viewModel.ExtraFields = extraFields.Distinct().ToList();
|
||||
|
||||
return PartialView("_ReportParameters", viewModel);
|
||||
}
|
||||
[TypeFilter(typeof(CollaboratorFilter))]
|
||||
public IActionResult GetVehicleHistory(int vehicleId)
|
||||
public IActionResult GetVehicleHistory(int vehicleId, ReportParameter reportParameter)
|
||||
{
|
||||
var vehicleHistory = new VehicleHistoryViewModel();
|
||||
vehicleHistory.ReportParameters = reportParameter;
|
||||
vehicleHistory.VehicleData = _dataAccess.GetVehicleById(vehicleId);
|
||||
var maxMileage = _vehicleLogic.GetMaxMileage(vehicleId);
|
||||
vehicleHistory.Odometer = maxMileage.ToString("N0");
|
||||
@@ -406,7 +432,8 @@ namespace CarCareTracker.Controllers
|
||||
Description = x.Description,
|
||||
Notes = x.Notes,
|
||||
Cost = x.Cost,
|
||||
DataType = ImportMode.ServiceRecord
|
||||
DataType = ImportMode.ServiceRecord,
|
||||
ExtraFields = x.ExtraFields
|
||||
}));
|
||||
//repair records
|
||||
reportData.AddRange(repairRecords.Select(x => new GenericReportModel
|
||||
@@ -416,7 +443,8 @@ namespace CarCareTracker.Controllers
|
||||
Description = x.Description,
|
||||
Notes = x.Notes,
|
||||
Cost = x.Cost,
|
||||
DataType = ImportMode.RepairRecord
|
||||
DataType = ImportMode.RepairRecord,
|
||||
ExtraFields = x.ExtraFields
|
||||
}));
|
||||
reportData.AddRange(upgradeRecords.Select(x => new GenericReportModel
|
||||
{
|
||||
@@ -425,7 +453,8 @@ namespace CarCareTracker.Controllers
|
||||
Description = x.Description,
|
||||
Notes = x.Notes,
|
||||
Cost = x.Cost,
|
||||
DataType = ImportMode.UpgradeRecord
|
||||
DataType = ImportMode.UpgradeRecord,
|
||||
ExtraFields = x.ExtraFields
|
||||
}));
|
||||
reportData.AddRange(taxRecords.Select(x => new GenericReportModel
|
||||
{
|
||||
@@ -434,7 +463,8 @@ namespace CarCareTracker.Controllers
|
||||
Description = x.Description,
|
||||
Notes = x.Notes,
|
||||
Cost = x.Cost,
|
||||
DataType = ImportMode.TaxRecord
|
||||
DataType = ImportMode.TaxRecord,
|
||||
ExtraFields = x.ExtraFields
|
||||
}));
|
||||
vehicleHistory.VehicleHistory = reportData.OrderBy(x => x.Date).ThenBy(x => x.Odometer).ToList();
|
||||
return PartialView("_VehicleHistory", vehicleHistory);
|
||||
@@ -528,5 +558,11 @@ namespace CarCareTracker.Controllers
|
||||
}).ToList();
|
||||
return PartialView("_GasCostByMonthReport", groupedRecord);
|
||||
}
|
||||
[HttpGet]
|
||||
public IActionResult GetAdditionalWidgets()
|
||||
{
|
||||
var widgets = _fileHelper.GetWidgets();
|
||||
return PartialView("_ReportWidgets", widgets);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,21 +7,21 @@ namespace CarCareTracker.Controllers
|
||||
{
|
||||
public partial class VehicleController
|
||||
{
|
||||
private List<string> CheckSupplyRecordsAvailability(List<SupplyUsage> supplyUsage)
|
||||
private List<SupplyAvailability> CheckSupplyRecordsAvailability(List<SupplyUsage> supplyUsage)
|
||||
{
|
||||
//returns empty string if all supplies are available
|
||||
var result = new List<string>();
|
||||
var result = new List<SupplyAvailability>();
|
||||
foreach (SupplyUsage supply in supplyUsage)
|
||||
{
|
||||
//get supply record.
|
||||
var supplyData = _supplyRecordDataAccess.GetSupplyRecordById(supply.SupplyId);
|
||||
if (supplyData == null)
|
||||
{
|
||||
result.Add("Missing Supplies, Please Delete This Template and Recreate It.");
|
||||
result.Add(new SupplyAvailability { Missing = true });
|
||||
}
|
||||
else if (supply.Quantity > supplyData.Quantity)
|
||||
else
|
||||
{
|
||||
result.Add($"Insufficient Quantity for {supplyData.Description}, need: {supply.Quantity}, available: {supplyData.Quantity}");
|
||||
result.Add(new SupplyAvailability { Missing = false, Description = supplyData.Description, Required = supply.Quantity, InStock = supplyData.Quantity });
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -494,6 +494,7 @@ namespace CarCareTracker.Controllers
|
||||
}
|
||||
return Json(result);
|
||||
}
|
||||
[HttpPost]
|
||||
public IActionResult DuplicateRecords(List<int> recordIds, ImportMode importMode)
|
||||
{
|
||||
bool result = false;
|
||||
@@ -573,6 +574,125 @@ namespace CarCareTracker.Controllers
|
||||
return Json(result);
|
||||
}
|
||||
[HttpPost]
|
||||
public IActionResult DuplicateRecordsToOtherVehicles(List<int> recordIds, List<int> vehicleIds, ImportMode importMode)
|
||||
{
|
||||
bool result = false;
|
||||
if (!recordIds.Any() || !vehicleIds.Any())
|
||||
{
|
||||
return Json(result);
|
||||
}
|
||||
foreach (int recordId in recordIds)
|
||||
{
|
||||
switch (importMode)
|
||||
{
|
||||
case ImportMode.ServiceRecord:
|
||||
{
|
||||
var existingRecord = _serviceRecordDataAccess.GetServiceRecordById(recordId);
|
||||
existingRecord.Id = default;
|
||||
foreach(int vehicleId in vehicleIds)
|
||||
{
|
||||
existingRecord.VehicleId = vehicleId;
|
||||
result = _serviceRecordDataAccess.SaveServiceRecordToVehicle(existingRecord);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ImportMode.RepairRecord:
|
||||
{
|
||||
var existingRecord = _collisionRecordDataAccess.GetCollisionRecordById(recordId);
|
||||
existingRecord.Id = default;
|
||||
foreach (int vehicleId in vehicleIds)
|
||||
{
|
||||
existingRecord.VehicleId = vehicleId;
|
||||
result = _collisionRecordDataAccess.SaveCollisionRecordToVehicle(existingRecord);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ImportMode.UpgradeRecord:
|
||||
{
|
||||
var existingRecord = _upgradeRecordDataAccess.GetUpgradeRecordById(recordId);
|
||||
existingRecord.Id = default;
|
||||
foreach (int vehicleId in vehicleIds)
|
||||
{
|
||||
existingRecord.VehicleId = vehicleId;
|
||||
result = _upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(existingRecord);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ImportMode.GasRecord:
|
||||
{
|
||||
var existingRecord = _gasRecordDataAccess.GetGasRecordById(recordId);
|
||||
existingRecord.Id = default;
|
||||
foreach (int vehicleId in vehicleIds)
|
||||
{
|
||||
existingRecord.VehicleId = vehicleId;
|
||||
result = _gasRecordDataAccess.SaveGasRecordToVehicle(existingRecord);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ImportMode.TaxRecord:
|
||||
{
|
||||
var existingRecord = _taxRecordDataAccess.GetTaxRecordById(recordId);
|
||||
existingRecord.Id = default;
|
||||
foreach (int vehicleId in vehicleIds)
|
||||
{
|
||||
existingRecord.VehicleId = vehicleId;
|
||||
result = _taxRecordDataAccess.SaveTaxRecordToVehicle(existingRecord);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ImportMode.SupplyRecord:
|
||||
{
|
||||
var existingRecord = _supplyRecordDataAccess.GetSupplyRecordById(recordId);
|
||||
existingRecord.Id = default;
|
||||
foreach (int vehicleId in vehicleIds)
|
||||
{
|
||||
existingRecord.VehicleId = vehicleId;
|
||||
result = _supplyRecordDataAccess.SaveSupplyRecordToVehicle(existingRecord);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ImportMode.NoteRecord:
|
||||
{
|
||||
var existingRecord = _noteDataAccess.GetNoteById(recordId);
|
||||
existingRecord.Id = default;
|
||||
foreach (int vehicleId in vehicleIds)
|
||||
{
|
||||
existingRecord.VehicleId = vehicleId;
|
||||
result = _noteDataAccess.SaveNoteToVehicle(existingRecord);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ImportMode.OdometerRecord:
|
||||
{
|
||||
var existingRecord = _odometerRecordDataAccess.GetOdometerRecordById(recordId);
|
||||
existingRecord.Id = default;
|
||||
foreach (int vehicleId in vehicleIds)
|
||||
{
|
||||
existingRecord.VehicleId = vehicleId;
|
||||
result = _odometerRecordDataAccess.SaveOdometerRecordToVehicle(existingRecord);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ImportMode.ReminderRecord:
|
||||
{
|
||||
var existingRecord = _reminderRecordDataAccess.GetReminderRecordById(recordId);
|
||||
existingRecord.Id = default;
|
||||
foreach (int vehicleId in vehicleIds)
|
||||
{
|
||||
existingRecord.VehicleId = vehicleId;
|
||||
result = _reminderRecordDataAccess.SaveReminderRecordToVehicle(existingRecord);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (result)
|
||||
{
|
||||
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Duplicated multiple {importMode.ToString()} - Ids: {string.Join(",", recordIds)} - to Vehicle Ids: {string.Join(",", vehicleIds)}");
|
||||
}
|
||||
return Json(result);
|
||||
}
|
||||
[HttpPost]
|
||||
public IActionResult GetGenericRecordModal(List<int> recordIds, ImportMode dataType)
|
||||
{
|
||||
var extraFields = _extraFieldDataAccess.GetExtraFieldsById((int)dataType).ExtraFields;
|
||||
|
||||
10
Enum/KioskMode.cs
Normal file
10
Enum/KioskMode.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public enum KioskMode
|
||||
{
|
||||
Vehicle = 0,
|
||||
Plan = 1,
|
||||
Reminder = 2,
|
||||
Cycle = 3
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Helper;
|
||||
using CarCareTracker.Models;
|
||||
using LiteDB;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Helper;
|
||||
using CarCareTracker.Models;
|
||||
using LiteDB;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using CarCareTracker.External.Interfaces;
|
||||
using CarCareTracker.Models;
|
||||
using Npgsql;
|
||||
using System.Net.Mail;
|
||||
|
||||
namespace CarCareTracker.External.Implementations
|
||||
{
|
||||
@@ -2,6 +2,7 @@
|
||||
using CarCareTracker.Models;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace CarCareTracker.Helper
|
||||
{
|
||||
@@ -14,6 +15,7 @@ namespace CarCareTracker.Helper
|
||||
bool AuthenticateRootUser(string username, string password);
|
||||
bool AuthenticateRootUserOIDC(string email);
|
||||
string GetWebHookUrl();
|
||||
bool GetCustomWidgetsEnabled();
|
||||
string GetMOTD();
|
||||
string GetLogoUrl();
|
||||
string GetServerLanguage();
|
||||
@@ -45,6 +47,10 @@ namespace CarCareTracker.Helper
|
||||
}
|
||||
return webhook;
|
||||
}
|
||||
public bool GetCustomWidgetsEnabled()
|
||||
{
|
||||
return bool.Parse(_config["LUBELOGGER_CUSTOM_WIDGETS"] ?? "false");
|
||||
}
|
||||
public string GetMOTD()
|
||||
{
|
||||
var motd = _config["LUBELOGGER_MOTD"];
|
||||
@@ -141,13 +147,13 @@ namespace CarCareTracker.Helper
|
||||
if (!File.Exists(StaticHelper.UserConfigPath))
|
||||
{
|
||||
//if file doesn't exist it might be because it's running on a mounted volume in docker.
|
||||
File.WriteAllText(StaticHelper.UserConfigPath, System.Text.Json.JsonSerializer.Serialize(new UserConfig()));
|
||||
File.WriteAllText(StaticHelper.UserConfigPath, JsonSerializer.Serialize(new UserConfig()));
|
||||
}
|
||||
var configFileContents = File.ReadAllText(StaticHelper.UserConfigPath);
|
||||
configData.EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)] ?? "false");
|
||||
configData.UserNameHash = _config[nameof(UserConfig.UserNameHash)] ?? string.Empty;
|
||||
configData.UserPasswordHash = _config[nameof(UserConfig.UserPasswordHash)] ?? string.Empty;
|
||||
File.WriteAllText(StaticHelper.UserConfigPath, System.Text.Json.JsonSerializer.Serialize(configData));
|
||||
File.WriteAllText(StaticHelper.UserConfigPath, JsonSerializer.Serialize(configData));
|
||||
_cache.Set<UserConfig>($"userConfig_{userId}", configData);
|
||||
return true;
|
||||
}
|
||||
@@ -185,6 +191,7 @@ namespace CarCareTracker.Helper
|
||||
EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)]),
|
||||
EnableRootUserOIDC = bool.Parse(_config[nameof(UserConfig.EnableRootUserOIDC)]),
|
||||
HideZero = bool.Parse(_config[nameof(UserConfig.HideZero)]),
|
||||
AutomaticDecimalFormat = bool.Parse(_config[nameof(UserConfig.AutomaticDecimalFormat)]),
|
||||
UseUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)]),
|
||||
UseMarkDownOnSavedNotes = bool.Parse(_config[nameof(UserConfig.UseMarkDownOnSavedNotes)]),
|
||||
UseThreeDecimalGasCost = bool.Parse(_config[nameof(UserConfig.UseThreeDecimalGasCost)]),
|
||||
|
||||
@@ -16,6 +16,10 @@ namespace CarCareTracker.Helper
|
||||
int ClearTempFolder();
|
||||
int ClearUnlinkedThumbnails(List<string> linkedImages);
|
||||
int ClearUnlinkedDocuments(List<string> linkedDocuments);
|
||||
string GetWidgets();
|
||||
bool WidgetsExist();
|
||||
bool SaveWidgets(string widgetsData);
|
||||
bool DeleteWidgets();
|
||||
}
|
||||
public class FileHelper : IFileHelper
|
||||
{
|
||||
@@ -100,6 +104,7 @@ namespace CarCareTracker.Helper
|
||||
var documentPath = Path.Combine(tempPath, "documents");
|
||||
var translationPath = Path.Combine(tempPath, "translations");
|
||||
var dataPath = Path.Combine(tempPath, StaticHelper.DbName);
|
||||
var widgetPath = Path.Combine(tempPath, StaticHelper.AdditionalWidgetsPath);
|
||||
var configPath = Path.Combine(tempPath, StaticHelper.UserConfigPath);
|
||||
if (Directory.Exists(imagePath))
|
||||
{
|
||||
@@ -174,6 +179,10 @@ namespace CarCareTracker.Helper
|
||||
//data path will always exist as it is created on startup if not.
|
||||
File.Move(dataPath, StaticHelper.DbName, true);
|
||||
}
|
||||
if (File.Exists(widgetPath))
|
||||
{
|
||||
File.Move(widgetPath, StaticHelper.AdditionalWidgetsPath, true);
|
||||
}
|
||||
if (File.Exists(configPath))
|
||||
{
|
||||
//check if config folder exists.
|
||||
@@ -223,6 +232,7 @@ namespace CarCareTracker.Helper
|
||||
var documentPath = Path.Combine(_webEnv.WebRootPath, "documents");
|
||||
var translationPath = Path.Combine(_webEnv.WebRootPath, "translations");
|
||||
var dataPath = StaticHelper.DbName;
|
||||
var widgetPath = StaticHelper.AdditionalWidgetsPath;
|
||||
var configPath = StaticHelper.UserConfigPath;
|
||||
if (!Directory.Exists(tempPath))
|
||||
Directory.CreateDirectory(tempPath);
|
||||
@@ -262,6 +272,12 @@ namespace CarCareTracker.Helper
|
||||
Directory.CreateDirectory(newPath);
|
||||
File.Copy(dataPath, $"{newPath}/{Path.GetFileName(dataPath)}");
|
||||
}
|
||||
if (File.Exists(widgetPath))
|
||||
{
|
||||
var newPath = Path.Combine(tempPath, "data");
|
||||
Directory.CreateDirectory(newPath);
|
||||
File.Copy(widgetPath, $"{newPath}/{Path.GetFileName(widgetPath)}");
|
||||
}
|
||||
if (File.Exists(configPath))
|
||||
{
|
||||
var newPath = Path.Combine(tempPath, "config");
|
||||
@@ -323,12 +339,20 @@ namespace CarCareTracker.Helper
|
||||
var tempPath = GetFullFilePath("temp", false);
|
||||
if (Directory.Exists(tempPath))
|
||||
{
|
||||
//delete files
|
||||
var files = Directory.GetFiles(tempPath);
|
||||
foreach (var file in files)
|
||||
{
|
||||
File.Delete(file);
|
||||
filesDeleted++;
|
||||
}
|
||||
//delete folders
|
||||
var folders = Directory.GetDirectories(tempPath);
|
||||
foreach(var folder in folders)
|
||||
{
|
||||
Directory.Delete(folder, true);
|
||||
filesDeleted++;
|
||||
}
|
||||
}
|
||||
return filesDeleted;
|
||||
}
|
||||
@@ -368,5 +392,57 @@ namespace CarCareTracker.Helper
|
||||
}
|
||||
return filesDeleted;
|
||||
}
|
||||
public string GetWidgets()
|
||||
{
|
||||
if (File.Exists(StaticHelper.AdditionalWidgetsPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
//read file
|
||||
var widgets = File.ReadAllText(StaticHelper.AdditionalWidgetsPath);
|
||||
return widgets;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
public bool WidgetsExist()
|
||||
{
|
||||
return File.Exists(StaticHelper.AdditionalWidgetsPath);
|
||||
}
|
||||
public bool SaveWidgets(string widgetsData)
|
||||
{
|
||||
try
|
||||
{
|
||||
//Delete Widgets if exists
|
||||
DeleteWidgets();
|
||||
File.WriteAllText(StaticHelper.AdditionalWidgetsPath, widgetsData);
|
||||
return true;
|
||||
} catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool DeleteWidgets()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(StaticHelper.AdditionalWidgetsPath))
|
||||
{
|
||||
File.Delete(StaticHelper.AdditionalWidgetsPath);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using CarCareTracker.Models;
|
||||
using MimeKit;
|
||||
using MailKit.Net.Smtp;
|
||||
using MailKit.Security;
|
||||
|
||||
namespace CarCareTracker.Helper
|
||||
{
|
||||
@@ -151,7 +152,7 @@ namespace CarCareTracker.Helper
|
||||
|
||||
using (var client = new SmtpClient())
|
||||
{
|
||||
client.Connect(server, mailConfig.Port, MailKit.Security.SecureSocketOptions.Auto);
|
||||
client.Connect(server, mailConfig.Port, SecureSocketOptions.Auto);
|
||||
//perform authentication if either username or password is provided.
|
||||
//do not perform authentication if neither are provided.
|
||||
if (!string.IsNullOrWhiteSpace(mailConfig.Username) || !string.IsNullOrWhiteSpace(mailConfig.Password)) {
|
||||
|
||||
@@ -9,15 +9,17 @@ namespace CarCareTracker.Helper
|
||||
/// </summary>
|
||||
public static class StaticHelper
|
||||
{
|
||||
public static string VersionNumber = "1.3.9";
|
||||
public static string VersionNumber = "1.4.0";
|
||||
public static string DbName = "data/cartracker.db";
|
||||
public static string UserConfigPath = "config/userConfig.json";
|
||||
public static string AdditionalWidgetsPath = "data/widgets.html";
|
||||
public static string GenericErrorMessage = "An error occurred, please try again later";
|
||||
public static string ReminderEmailTemplate = "defaults/reminderemailtemplate.txt";
|
||||
public static string DefaultAllowedFileExtensions = ".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx";
|
||||
public static string SponsorsPath = "https://hargata.github.io/hargata/sponsors.json";
|
||||
public static string TranslationPath = "https://hargata.github.io/lubelog_translations";
|
||||
public static string TranslationDirectoryPath = $"{TranslationPath}/directory.json";
|
||||
public const string ReportNote = "Report generated by LubeLogger, a Free and Open Source Vehicle Maintenance Tracker - LubeLogger.com";
|
||||
public static string GetTitleCaseReminderUrgency(ReminderUrgency input)
|
||||
{
|
||||
switch (input)
|
||||
@@ -32,6 +34,66 @@ 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 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)
|
||||
{
|
||||
|
||||
@@ -107,7 +107,10 @@ namespace CarCareTracker.Helper
|
||||
{
|
||||
foreach(var translation in translationDictionary)
|
||||
{
|
||||
defaultTranslation[translation.Key] = translation.Value;
|
||||
if (defaultTranslation.ContainsKey(translation.Key))
|
||||
{
|
||||
defaultTranslation[translation.Key] = translation.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultTranslation ?? new Dictionary<string, string>();
|
||||
@@ -123,7 +126,9 @@ namespace CarCareTracker.Helper
|
||||
}
|
||||
public OperationResponse SaveTranslation(string userLanguage, Dictionary<string, string> translations)
|
||||
{
|
||||
if (userLanguage == "en_US")
|
||||
bool create = bool.Parse(_config["LUBELOGGER_TRANSLATOR"] ?? "false");
|
||||
bool isDefaultLanguage = userLanguage == "en_US";
|
||||
if (isDefaultLanguage && !create)
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "The translation file name en_US is reserved." };
|
||||
}
|
||||
@@ -135,7 +140,7 @@ namespace CarCareTracker.Helper
|
||||
{
|
||||
return new OperationResponse { Success = false, Message = "Translation has no data." };
|
||||
}
|
||||
var translationFilePath = _fileHelper.GetFullFilePath($"/translations/{userLanguage}.json", false);
|
||||
var translationFilePath = isDefaultLanguage ? _fileHelper.GetFullFilePath($"/defaults/en_US.json") : _fileHelper.GetFullFilePath($"/translations/{userLanguage}.json", false);
|
||||
try
|
||||
{
|
||||
if (File.Exists(translationFilePath))
|
||||
@@ -145,6 +150,12 @@ namespace CarCareTracker.Helper
|
||||
_cache.Remove($"lang_{userLanguage}"); //clear out cache, force a reload from file.
|
||||
} else
|
||||
{
|
||||
//check if directory exists first.
|
||||
var translationDirectory = _fileHelper.GetFullFilePath("translations/", false);
|
||||
if (!Directory.Exists(translationDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(translationDirectory);
|
||||
}
|
||||
//write to file
|
||||
File.WriteAllText(translationFilePath, JsonSerializer.Serialize(translations));
|
||||
}
|
||||
@@ -167,7 +178,13 @@ namespace CarCareTracker.Helper
|
||||
Directory.CreateDirectory(uploadDirectory);
|
||||
}
|
||||
var saveFilePath = _fileHelper.GetFullFilePath(tempFileName, false);
|
||||
File.WriteAllText(saveFilePath, JsonSerializer.Serialize(translations));
|
||||
//standardize translation format for export only.
|
||||
Dictionary<string, string> sortedTranslations = new Dictionary<string, string>();
|
||||
foreach (var translation in translations.OrderBy(x => x.Key))
|
||||
{
|
||||
sortedTranslations.Add(translation.Key, translation.Value);
|
||||
};
|
||||
File.WriteAllText(saveFilePath, JsonSerializer.Serialize(sortedTranslations, new JsonSerializerOptions { WriteIndented = true }));
|
||||
return tempFileName;
|
||||
}
|
||||
catch(Exception ex)
|
||||
|
||||
@@ -14,6 +14,9 @@ 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);
|
||||
List<ReminderRecordViewModel> GetReminders(List<Vehicle> vehicles, bool isCalendar);
|
||||
List<PlanRecord> GetPlans(List<Vehicle> vehicles, bool excludeDone);
|
||||
}
|
||||
public class VehicleLogic: IVehicleLogic
|
||||
{
|
||||
@@ -24,6 +27,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 +37,7 @@ namespace CarCareTracker.Logic
|
||||
ITaxRecordDataAccess taxRecordDataAccess,
|
||||
IOdometerRecordDataAccess odometerRecordDataAccess,
|
||||
IReminderRecordDataAccess reminderRecordDataAccess,
|
||||
IPlanRecordDataAccess planRecordDataAccess,
|
||||
IReminderHelper reminderHelper
|
||||
) {
|
||||
_serviceRecordDataAccess = serviceRecordDataAccess;
|
||||
@@ -41,6 +46,7 @@ namespace CarCareTracker.Logic
|
||||
_upgradeRecordDataAccess = upgradeRecordDataAccess;
|
||||
_taxRecordDataAccess = taxRecordDataAccess;
|
||||
_odometerRecordDataAccess = odometerRecordDataAccess;
|
||||
_planRecordDataAccess = planRecordDataAccess;
|
||||
_reminderRecordDataAccess = reminderRecordDataAccess;
|
||||
_reminderHelper = reminderHelper;
|
||||
}
|
||||
@@ -218,5 +224,98 @@ 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;
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
|
||||
@@ -42,7 +43,7 @@ namespace CarCareTracker.Middleware
|
||||
new(ClaimTypes.Role, nameof(UserData.IsRootUser))
|
||||
};
|
||||
appIdentity.AddClaims(userIdentity);
|
||||
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), this.Scheme.Name);
|
||||
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), Scheme.Name);
|
||||
return AuthenticateResult.Success(ticket);
|
||||
}
|
||||
else
|
||||
@@ -59,7 +60,7 @@ namespace CarCareTracker.Middleware
|
||||
{
|
||||
var cleanedHeader = request_header.ToString().Replace("Basic ", "").Trim();
|
||||
byte[] data = Convert.FromBase64String(cleanedHeader);
|
||||
string decodedString = System.Text.Encoding.UTF8.GetString(data);
|
||||
string decodedString = Encoding.UTF8.GetString(data);
|
||||
var splitString = decodedString.Split(":");
|
||||
if (splitString.Count() != 2)
|
||||
{
|
||||
@@ -85,7 +86,7 @@ namespace CarCareTracker.Middleware
|
||||
userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsRootUser)));
|
||||
}
|
||||
appIdentity.AddClaims(userIdentity);
|
||||
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), this.Scheme.Name);
|
||||
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), Scheme.Name);
|
||||
return AuthenticateResult.Success(ticket);
|
||||
}
|
||||
}
|
||||
@@ -133,7 +134,7 @@ namespace CarCareTracker.Middleware
|
||||
userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsRootUser)));
|
||||
}
|
||||
appIdentity.AddClaims(userIdentity);
|
||||
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), this.Scheme.Name);
|
||||
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), Scheme.Name);
|
||||
return AuthenticateResult.Success(ticket);
|
||||
}
|
||||
}
|
||||
|
||||
14
Models/Kiosk/KioskViewModel.cs
Normal file
14
Models/Kiosk/KioskViewModel.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -12,5 +12,6 @@
|
||||
public string Notes { get; set; }
|
||||
public decimal Cost { get; set; }
|
||||
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
|
||||
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
|
||||
}
|
||||
}
|
||||
|
||||
8
Models/Report/ReportParameter.cs
Normal file
8
Models/Report/ReportParameter.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class ReportParameter
|
||||
{
|
||||
public List<string> VisibleColumns { get; set; } = new List<string>();
|
||||
public List<string> ExtraFields { get; set; } = new List<string>();
|
||||
}
|
||||
}
|
||||
@@ -8,5 +8,6 @@
|
||||
public ReminderMakeUpForVehicle ReminderMakeUpForVehicle { get; set; } = new ReminderMakeUpForVehicle();
|
||||
public List<int> Years { get; set; } = new List<int>();
|
||||
public List<UserCollaborator> Collaborators { get; set; } = new List<UserCollaborator>();
|
||||
public bool CustomWidgetsConfigured { get; set; } = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
{
|
||||
public Vehicle VehicleData { get; set; }
|
||||
public List<GenericReportModel> VehicleHistory { get; set; }
|
||||
public ReportParameter ReportParameters { get; set; }
|
||||
public string Odometer { get; set; }
|
||||
public string MPG { get; set; }
|
||||
public decimal TotalCost { get; set; }
|
||||
|
||||
11
Models/Supply/SupplyAvailability.cs
Normal file
11
Models/Supply/SupplyAvailability.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace CarCareTracker.Models
|
||||
{
|
||||
public class SupplyAvailability
|
||||
{
|
||||
public bool Missing { get; set; }
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public decimal Required { get; set; }
|
||||
public decimal InStock { get; set; }
|
||||
public bool Insufficient { get { return Required > InStock; } }
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
public bool EnableShopSupplies { get; set; }
|
||||
public bool EnableExtraFieldColumns { get; set; }
|
||||
public bool HideSoldVehicles { get; set; }
|
||||
public bool AutomaticDecimalFormat { get; set; }
|
||||
public string PreferredGasUnit { get; set; } = string.Empty;
|
||||
public string PreferredGasMileageUnit { get; set; } = string.Empty;
|
||||
public List<UserColumnPreference> UserColumnPreferences { get; set; } = new List<UserColumnPreference>();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var enableAuth = userConfig.EnableAuth;
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
var logoUrl = config.GetLogoUrl();
|
||||
}
|
||||
@model string
|
||||
@{
|
||||
@@ -62,7 +61,7 @@
|
||||
<div class="container">
|
||||
<div class="row mt-2">
|
||||
<div class="d-flex lubelogger-navbar">
|
||||
<img src="@logoUrl" />
|
||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||
<div class="lubelogger-navbar-button">
|
||||
<button type="button" class="btn btn-adaptive" onclick="showMobileNav()"><i class="bi bi-list lubelogger-menu-icon"></i></button>
|
||||
</div>
|
||||
|
||||
148
Views/Home/Kiosk.cshtml
Normal file
148
Views/Home/Kiosk.cshtml
Normal file
@@ -0,0 +1,148 @@
|
||||
@{
|
||||
ViewData["Title"] = "Kiosk";
|
||||
}
|
||||
@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>
|
||||
<div id="kioskContainer" class="container-fluid">
|
||||
</div>
|
||||
<script>
|
||||
let refreshTimer;
|
||||
let exceptionList = [];
|
||||
let subtractAmount = 0;
|
||||
let kioskMode = '@Model.KioskMode';
|
||||
let currentKioskMode = 'Plan';
|
||||
let kioskWakeLock;
|
||||
|
||||
@foreach(int exception in Model.Exclusions)
|
||||
{
|
||||
@: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();
|
||||
//acquire wakeLock;
|
||||
try {
|
||||
navigator.wakeLock.request('screen').then((wl) => {
|
||||
kioskWakeLock = wl;
|
||||
});
|
||||
} catch (err) {
|
||||
errorToast('Location Services not Enabled');
|
||||
}
|
||||
}
|
||||
function setAccessToken(accessToken){
|
||||
//use this function to never worry about user session expiring.
|
||||
$.ajaxSetup({
|
||||
headers: {
|
||||
'Authorization': `Basic ${accessToken}`
|
||||
}
|
||||
});
|
||||
console.log("Access Token for Kiosk Mode Configured!");
|
||||
}
|
||||
function retrieveKioskContent(){
|
||||
clearInterval(refreshTimer);
|
||||
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 () {
|
||||
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);
|
||||
if (kioskMode == 'Cycle') {
|
||||
//remove the vehicle programmatically.
|
||||
$(`[data-vehicleId=${vehicleId}]`).remove();
|
||||
} else {
|
||||
//force a refresh
|
||||
retrieveKioskContent();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
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>
|
||||
@@ -1,6 +0,0 @@
|
||||
@{
|
||||
ViewData["Title"] = "Privacy Policy";
|
||||
}
|
||||
<h1>@ViewData["Title"]</h1>
|
||||
|
||||
<p>Use this page to detail your site's privacy policy.</p>
|
||||
126
Views/Home/_Kiosk.cshtml
Normal file
126
Views/Home/_Kiosk.cshtml
Normal file
@@ -0,0 +1,126 @@
|
||||
@using CarCareTracker.Helper
|
||||
@model List<VehicleInfo>
|
||||
@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 (VehicleInfo vehicle in Model)
|
||||
{
|
||||
<div class="col" data-vehicleId="@vehicle.VehicleData.Id">
|
||||
<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>
|
||||
}
|
||||
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>
|
||||
}
|
||||
86
Views/Home/_KioskPlan.cshtml
Normal file
86
Views/Home/_KioskPlan.cshtml
Normal 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>
|
||||
}
|
||||
47
Views/Home/_KioskReminder.cshtml
Normal file
47
Views/Home/_KioskReminder.cshtml
Normal 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>
|
||||
}
|
||||
@@ -37,10 +37,14 @@
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useDescending" checked="@Model.UserConfig.UseDescending">
|
||||
<label class="form-check-label" for="useDescending">@translator.Translate(userLanguage, "Sort lists in Descending Order(Newest to Oldest)")</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="hideZero" checked="@Model.UserConfig.HideZero">
|
||||
<label class="form-check-label" for="hideZero">@translator.Translate(userLanguage, "Replace $0.00 Costs with ---")</label>
|
||||
</div>
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="automaticDecimalFormat" checked="@Model.UserConfig.AutomaticDecimalFormat">
|
||||
<label class="form-check-label" for="automaticDecimalFormat">@translator.Translate(userLanguage, "Automatically Format Decimal Inputs")</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="useThreeDecimal" checked="@Model.UserConfig.UseThreeDecimalGasCost">
|
||||
<label class="form-check-label" for="useThreeDecimal">@translator.Translate(userLanguage, "Use Three Decimals For Fuel Cost")</label>
|
||||
@@ -308,6 +312,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>
|
||||
@@ -330,6 +335,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" data-bs-focus="false" id="customWidgetModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content" id="customWidgetModalContent">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" data-bs-focus="false" id="tabReorderModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content" id="tabReorderModalContent">
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@inject IConfiguration serverConfig;
|
||||
@model Dictionary<string, string>
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
bool showDelete = bool.Parse(serverConfig["LUBELOGGER_TRANSLATOR"] ?? "false");
|
||||
}
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="translationEditorModalLabel">@translator.Translate(userLanguage, "Translation Editor")</h5>
|
||||
@@ -15,8 +17,15 @@
|
||||
<div class="form-group" style="max-height:50vh; overflow-x:hidden; overflow-y:scroll;">
|
||||
@foreach(var translation in Model)
|
||||
{
|
||||
<div class="row translation-keyvalue mb-2">
|
||||
<div class="col-md-6 col-12 translation-key">@translation.Key.Replace("_", " ")</div>
|
||||
<div class="row translation-keyvalue mb-2 align-items-center">
|
||||
@if (showDelete)
|
||||
{
|
||||
<div class="col-md-1 col-1 translation-delete"><button onclick="deleteTranslationKey(this)" class="btn text-danger btn-sm"><i class="bi bi-x-lg"></i></button></div>
|
||||
<div class="col-md-5 col-11 translation-key">@translation.Key.Replace("_", " ")</div>
|
||||
} else
|
||||
{
|
||||
<div class="col-md-6 col-12 translation-key">@translation.Key.Replace("_", " ")</div>
|
||||
}
|
||||
<div class="col-md-6 col-12 translation-value">
|
||||
<textarea style="height:100%;width:98%;" class="form-control" placeholder="@translation.Value">@translation.Value</textarea>
|
||||
</div>
|
||||
|
||||
26
Views/Home/_VehicleSelector.cshtml
Normal file
26
Views/Home/_VehicleSelector.cshtml
Normal file
@@ -0,0 +1,26 @@
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@using CarCareTracker.Helper
|
||||
@model List<Vehicle>
|
||||
|
||||
@{
|
||||
var userLanguage = config.GetUserConfig(User).UserLanguage;
|
||||
}
|
||||
|
||||
@if (Model.Any())
|
||||
{
|
||||
<div id="vehicleSelector">
|
||||
<ul class="list-group">
|
||||
@foreach (Vehicle vehicle in Model)
|
||||
{
|
||||
<li class="list-group-item text-start">
|
||||
<input class="form-check-input" type="checkbox" value="@vehicle.Id" id="vehicleCheck_@vehicle.Id">
|
||||
<label class="form-check-label stretched-link" for="vehicleCheck_@vehicle.Id">@($"{vehicle.Year} {vehicle.Make} {vehicle.Model} ({StaticHelper.GetVehicleIdentifier(vehicle)})")</label>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
} else
|
||||
{
|
||||
<div id="vehicleSelector"><span class="lead">@translator.Translate(userLanguage, "No Vehicles Available")</span></div>
|
||||
}
|
||||
28
Views/Home/_WidgetEditor.cshtml
Normal file
28
Views/Home/_WidgetEditor.cshtml
Normal file
@@ -0,0 +1,28 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@model string
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="widgetEditorModalLabel">@translator.Translate(userLanguage, "Custom Widgets Editor")</h5>
|
||||
<button type="button" class="btn-close" onclick="hideCustomWidgets()" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body" onkeydown="handleEnter(this)">
|
||||
<form class="form-inline">
|
||||
<div class="form-group" style="height:50vh; overflow-x:hidden; overflow-y:auto;">
|
||||
<div class="row">
|
||||
<div class="col-12" style="height:48vh;">
|
||||
<textarea id="widgetEditor" style="width:100%; height:100%;">@Model</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-danger me-auto" onclick="deleteCustomWidgets()">@translator.Translate(userLanguage, "Delete")</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="hideCustomWidgets()">@translator.Translate(userLanguage, "Cancel")</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveCustomWidgets()">@translator.Translate(userLanguage, "Save")</button>
|
||||
</div>
|
||||
@@ -2,7 +2,6 @@
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var logoUrl = config.GetLogoUrl();
|
||||
var userLanguage = config.GetServerLanguage();
|
||||
}
|
||||
@{
|
||||
@@ -14,7 +13,7 @@
|
||||
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
||||
<div>
|
||||
<div style="max-width:204px;">
|
||||
<img src="@logoUrl" />
|
||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||
<div class="form-group">
|
||||
<label for="inputUserName">@translator.Translate(userLanguage, "Username")</label>
|
||||
<input type="text" id="inputUserName" class="form-control">
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
@inject ITranslationHelper translator
|
||||
@model string
|
||||
@{
|
||||
var logoUrl = config.GetLogoUrl();
|
||||
var userLanguage = config.GetServerLanguage();
|
||||
var registrationDisabled = config.GetServerDisabledRegistration();
|
||||
var openIdConfigName = config.GetOpenIDConfig().Name;
|
||||
@@ -17,7 +16,7 @@
|
||||
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
||||
<div>
|
||||
<div style="max-width:204px;">
|
||||
<img src="@logoUrl" />
|
||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||
<div class="form-group">
|
||||
<label for="inputUserName">@translator.Translate(userLanguage, "Username")</label>
|
||||
<input type="text" id="inputUserName" class="form-control">
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var logoUrl = config.GetLogoUrl();
|
||||
var userLanguage = config.GetServerLanguage();
|
||||
}
|
||||
@model string
|
||||
@@ -15,7 +14,7 @@
|
||||
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
||||
<div>
|
||||
<div style="max-width:204px;">
|
||||
<img src="@logoUrl" />
|
||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||
<div class="form-group">
|
||||
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
|
||||
<input type="text" id="inputToken" class="form-control">
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var logoUrl = config.GetLogoUrl();
|
||||
var userLanguage = config.GetServerLanguage();
|
||||
}
|
||||
@{
|
||||
@@ -14,7 +13,7 @@
|
||||
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
||||
<div>
|
||||
<div style="max-width:204px;">
|
||||
<img src="@logoUrl" />
|
||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||
<div class="form-group">
|
||||
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
|
||||
<input type="text" id="inputToken" class="form-control">
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var logoUrl = config.GetLogoUrl();
|
||||
var userLanguage = config.GetServerLanguage();
|
||||
}
|
||||
@{
|
||||
@@ -14,7 +13,7 @@
|
||||
<div class="container d-flex align-items-center justify-content-center" style="height:100vh">
|
||||
<div>
|
||||
<div style="max-width:204px;">
|
||||
<img src="@logoUrl" />
|
||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||
<div class="form-group">
|
||||
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
|
||||
<input type="text" id="inputToken" class="form-control">
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
@using CarCareTracker.Helper
|
||||
@{
|
||||
@{
|
||||
ViewData["Title"] = "Database Migration";
|
||||
}
|
||||
@inject IConfiguration config;
|
||||
@@ -7,6 +6,7 @@
|
||||
@{
|
||||
var userLanguage = config[nameof(UserConfig.UserLanguage)] ?? "en_US";
|
||||
}
|
||||
@using CarCareTracker.Helper
|
||||
@model AdminViewModel
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
@@ -21,9 +21,9 @@
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">Instructions</li>
|
||||
<li class="list-group-item">Use this tool to migrate data between LiteDB and Postgres</li>
|
||||
<li class="list-group-item">Note that it is recommended that the Postgres DB is empty when importing from LiteDB to prevent primary key errors.</li>
|
||||
<li class="list-group-item">@translator.Translate(userLanguage, "Instructions")</li>
|
||||
<li class="list-group-item">@translator.Translate(userLanguage, "Use this tool to migrate data between LiteDB and Postgres")</li>
|
||||
<li class="list-group-item">@translator.Translate(userLanguage, "Note that it is recommended that the Postgres DB is empty when importing from LiteDB to prevent primary key errors.")</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@using CarCareTracker.Helper
|
||||
@using System.Globalization
|
||||
<!DOCTYPE html>
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@@ -10,9 +11,10 @@
|
||||
var useMPG = userConfig.UseMPG;
|
||||
var useMarkDown = userConfig.UseMarkDownOnSavedNotes;
|
||||
var useThreeDecimals = userConfig.UseThreeDecimalGasCost;
|
||||
var shortDatePattern = System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
|
||||
var firstDayOfWeek = (int)System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek;
|
||||
var numberFormat = System.Globalization.CultureInfo.CurrentCulture.NumberFormat;
|
||||
var automaticDecimalFormat = userConfig.AutomaticDecimalFormat;
|
||||
var shortDatePattern = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
|
||||
var firstDayOfWeek = (int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek;
|
||||
var numberFormat = CultureInfo.CurrentCulture.NumberFormat;
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
shortDatePattern = shortDatePattern.ToLower();
|
||||
if (!shortDatePattern.Contains("dd"))
|
||||
@@ -90,6 +92,42 @@
|
||||
input = input.replace(".", decimalSeparator);
|
||||
return input;
|
||||
}
|
||||
function fixDecimalInput(input, numOfDecimals) {
|
||||
if ("@automaticDecimalFormat" == "True") {
|
||||
//get the decimal separator.
|
||||
var decimalSeparator = decodeHTMLEntities("@numberFormat.NumberDecimalSeparator");
|
||||
var inputText = $(input).val().trim();
|
||||
inputText = inputText.replace(decimalSeparator, '');
|
||||
inputText = +inputText;
|
||||
if (isNaN(inputText)){
|
||||
return;
|
||||
}
|
||||
inputText = inputText.toString();
|
||||
if (inputText == '') {
|
||||
return;
|
||||
};
|
||||
//check number of decimals.
|
||||
if (inputText.length <= numOfDecimals) {
|
||||
//less than number of decimals, assume everything is behind the decimal.
|
||||
inputText = `0${decimalSeparator}${inputText}`; //add leading zero.
|
||||
$(input).val(inputText);
|
||||
} else {
|
||||
//check if leading zero
|
||||
var charToSlice = numOfDecimals * -1;
|
||||
var charsBehindDecimal = inputText.slice(charToSlice);
|
||||
var charsFrontDecimal = inputText.substr(0, inputText.length - numOfDecimals);
|
||||
inputText = `${charsFrontDecimal}${decimalSeparator}${charsBehindDecimal}`;
|
||||
$(input).val(inputText);
|
||||
}
|
||||
}
|
||||
}
|
||||
function interceptDecimalKeys(event) {
|
||||
if ("@automaticDecimalFormat" == "True") {
|
||||
if (event.which == 190 || event.which == 188) {
|
||||
event.preventDefault(); //intercept keys.
|
||||
}
|
||||
}
|
||||
}
|
||||
function genericErrorMessage(){
|
||||
return decodeHTMLEntities('@translator.Translate(userLanguage, "An error has occurred, please try again later")');
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
</div>
|
||||
}
|
||||
<label for="collisionRecordCost">@translator.Translate(userLanguage, "Cost")</label>
|
||||
<input type="text" inputmode="decimal" id="collisionRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of the repair")" value="@(isNew ? "" : Model.Cost)">
|
||||
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="collisionRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of the repair")" value="@(isNew ? "" : Model.Cost)">
|
||||
@if (isNew)
|
||||
{
|
||||
@await Html.PartialAsync("_SupplyStore", "RepairRecord")
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
<div class="col-12">
|
||||
<div class="row mt-2 showOnPrint">
|
||||
<div class="d-flex">
|
||||
<img src="/defaults/lubelogger_logo.png" />
|
||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-hover">
|
||||
@@ -126,7 +126,7 @@
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@(collisionRecord.Mileage == default ? "---" : collisionRecord.Mileage.ToString())</td>
|
||||
<td class="col-3 col-xl-4 flex-grow-1 flex-shrink-1 text-truncate" data-column="description">@collisionRecord.Description</td>
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@((hideZero && collisionRecord.Cost == default) ? "---" : collisionRecord.Cost.ToString("C"))</td>
|
||||
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@CarCareTracker.Helper.StaticHelper.TruncateStrings(collisionRecord.Notes)</td>
|
||||
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@StaticHelper.TruncateStrings(collisionRecord.Notes)</td>
|
||||
@foreach (string extraFieldColumn in extraFields)
|
||||
{
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">
|
||||
@@ -134,7 +134,7 @@
|
||||
var extraFieldValue = collisionRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "";
|
||||
if (!string.IsNullOrWhiteSpace(extraFieldValue) && Uri.IsWellFormedUriString(extraFieldValue, UriKind.Absolute))
|
||||
{
|
||||
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@CarCareTracker.Helper.StaticHelper.TruncateStrings(extraFieldValue)</a>
|
||||
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@StaticHelper.TruncateStrings(extraFieldValue)</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -145,6 +145,11 @@
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
<tr class="d-flex">
|
||||
<td class="col-12 showOnPrint lubelogger-report-banner">
|
||||
@StaticHelper.ReportNote
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -168,6 +173,7 @@
|
||||
<li><a class="dropdown-item" href="#" onclick="moveRecords(selectedRow, 'RepairRecord', 'UpgradeRecord')">@translator.Translate(userLanguage, "Upgrades")</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'RepairRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'RepairRecord')">@translator.Translate(userLanguage, "Duplicate To Vehicle")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'RepairRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
|
||||
<li><hr class="context-menu-active-multiple dropdown-divider"></li>
|
||||
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="getRecordsDeltaStats(selectedRow)">@translator.Translate(userLanguage, "Statistics")</a></li>
|
||||
|
||||
@@ -165,7 +165,7 @@
|
||||
<div class="col-12">
|
||||
<div class="row mt-2 showOnPrint">
|
||||
<div class="d-flex">
|
||||
<img src="/defaults/lubelogger_logo.png" />
|
||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-hover">
|
||||
@@ -204,7 +204,7 @@
|
||||
var extraFieldValue = gasRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "";
|
||||
if (!string.IsNullOrWhiteSpace(extraFieldValue) && Uri.IsWellFormedUriString(extraFieldValue, UriKind.Absolute))
|
||||
{
|
||||
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@CarCareTracker.Helper.StaticHelper.TruncateStrings(extraFieldValue)</a>
|
||||
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@StaticHelper.TruncateStrings(extraFieldValue)</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -215,6 +215,11 @@
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
<tr class="d-flex">
|
||||
<td class="col-12 showOnPrint lubelogger-report-banner">
|
||||
@StaticHelper.ReportNote
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -234,6 +239,7 @@
|
||||
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="editMultipleGasRecords(selectedRow)">@translator.Translate(userLanguage, "Edit Multiple")</a></li>
|
||||
<li><hr class="context-menu-multiple dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'GasRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'GasRecord')">@translator.Translate(userLanguage, "Duplicate To Vehicle")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'GasRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
|
||||
<li><hr class="context-menu-odometer-adjustment dropdown-divider"></li>
|
||||
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'GasRecord')">@translator.Translate(userLanguage, "Adjust Odometer")</a></li>
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
}
|
||||
</div>
|
||||
<label for="gasRecordGallons">@($"{translator.Translate(userLanguage, "Fuel Consumption")}({consumptionUnit})")</label>
|
||||
<input type="text" inputmode="decimal" id="gasRecordGallons" class="form-control" placeholder="@translator.Translate(userLanguage,"Amount of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Gallons)">
|
||||
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 3)" id="gasRecordGallons" class="form-control" placeholder="@translator.Translate(userLanguage,"Amount of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Gallons)">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="gasIsFillToFull" checked="@Model.GasRecord.IsFillToFull">
|
||||
<label class="form-check-label" for="gasIsFillToFull">@translator.Translate(userLanguage,"Is Filled To Full")</label>
|
||||
@@ -75,7 +75,7 @@
|
||||
@if (isNew)
|
||||
{
|
||||
<div class="input-group">
|
||||
<input type="text" inputmode="decimal" id="gasRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Cost)">
|
||||
<input type="text" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 3)" inputmode="decimal" id="gasRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Cost)">
|
||||
<div class="input-group-text">
|
||||
<select class="form-select form-select-sm" id="gasCostType">
|
||||
<option value="total">@translator.Translate(userLanguage,"Total")</option>
|
||||
@@ -85,7 +85,7 @@
|
||||
</div>
|
||||
} else
|
||||
{
|
||||
<input type="text" inputmode="decimal" id="gasRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Cost)">
|
||||
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 3)" id="gasRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of gas refueled")" value="@(isNew ? "" : Model.GasRecord.Cost)">
|
||||
}
|
||||
<label for="gasRecordTag">@translator.Translate(userLanguage,"Tags(optional)")</label>
|
||||
<select multiple class="form-select" id="gasRecordTag">
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
<label for="gasRecordMileage">@translator.Translate(userLanguage, "Odometer")</label>
|
||||
<input type="number" inputmode="numeric" id="gasRecordMileage" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||
<label for="gasRecordConsumption">@translator.Translate(userLanguage, "Fuel Consumption")</label>
|
||||
<input type="text" inputmode="decimal" id="gasRecordConsumption" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 3)" id="gasRecordConsumption" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||
<label for="gasRecordCost">@translator.Translate(userLanguage, "Cost")</label>
|
||||
<input type="text" inputmode="decimal" id="gasRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 3)" id="gasRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||
<label for="gasRecordTag">@translator.Translate(userLanguage, "Tags(use --- to clear all existing tags)")</label>
|
||||
<select multiple class="form-select" id="gasRecordTag"></select>
|
||||
@foreach (ExtraField field in Model.EditRecord.ExtraFields)
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<label for="genericRecordDescription">@translator.Translate(userLanguage, "Description")</label>
|
||||
<input type="text" id="genericRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||
<label for="genericRecordCost">@translator.Translate(userLanguage, "Cost")</label>
|
||||
<input type="text" inputmode="decimal" id="genericRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="genericRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"(multiple)")">
|
||||
<label for="genericRecordTag">@translator.Translate(userLanguage, "Tags(use --- to clear all existing tags)")</label>
|
||||
<select multiple class="form-select" id="genericRecordTag"></select>
|
||||
@foreach (ExtraField field in Model.EditRecord.ExtraFields)
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<div class="col-12">
|
||||
<div class="row mt-2 showOnPrint">
|
||||
<div class="d-flex">
|
||||
<img src="/defaults/lubelogger_logo.png" />
|
||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-hover">
|
||||
@@ -52,9 +52,14 @@
|
||||
{
|
||||
<td class="col-3">@note.Description</td>
|
||||
}
|
||||
<td class="col-9 text-truncate" data-record-type="cost">@CarCareTracker.Helper.StaticHelper.TruncateStrings(note.NoteText, 100)</td>
|
||||
<td class="col-9 text-truncate" data-record-type="cost">@StaticHelper.TruncateStrings(note.NoteText, 100)</td>
|
||||
</tr>
|
||||
}
|
||||
<tr class="d-flex">
|
||||
<td class="col-12 showOnPrint lubelogger-report-banner">
|
||||
@StaticHelper.ReportNote
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -77,5 +82,6 @@
|
||||
<li><a class="dropdown-item" href="#" onclick="pinNotes(selectedRow, false, false)">@translator.Translate(userLanguage, "Unpin")</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'NoteRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'NoteRecord')">@translator.Translate(userLanguage, "Duplicate To Vehicle")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'NoteRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
|
||||
</ul>
|
||||
@@ -101,7 +101,7 @@
|
||||
<div class="col-12">
|
||||
<div class="row mt-2 showOnPrint">
|
||||
<div class="d-flex">
|
||||
<img src="/defaults/lubelogger_logo.png" />
|
||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-hover">
|
||||
@@ -126,7 +126,7 @@
|
||||
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="initialodometer">@odometerRecord.InitialMileage</td>
|
||||
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@odometerRecord.Mileage</td>
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="distance" data-record-type="distance">@(odometerRecord.DistanceTraveled == default ? "---" : odometerRecord.DistanceTraveled)</td>
|
||||
<td class="col-2 col-xl-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@CarCareTracker.Helper.StaticHelper.TruncateStrings(odometerRecord.Notes, 75)</td>
|
||||
<td class="col-2 col-xl-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@StaticHelper.TruncateStrings(odometerRecord.Notes, 75)</td>
|
||||
@foreach (string extraFieldColumn in extraFields)
|
||||
{
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">
|
||||
@@ -134,7 +134,7 @@
|
||||
var extraFieldValue = odometerRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "";
|
||||
if (!string.IsNullOrWhiteSpace(extraFieldValue) && Uri.IsWellFormedUriString(extraFieldValue, UriKind.Absolute))
|
||||
{
|
||||
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@CarCareTracker.Helper.StaticHelper.TruncateStrings(extraFieldValue)</a>
|
||||
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@StaticHelper.TruncateStrings(extraFieldValue)</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -145,6 +145,11 @@
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
<tr class="d-flex">
|
||||
<td class="col-12 showOnPrint lubelogger-report-banner">
|
||||
@StaticHelper.ReportNote
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -166,6 +171,7 @@
|
||||
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="recalculateDistance()">@translator.Translate(userLanguage, "Recalculate Distance")</a></li>
|
||||
<li><hr class="context-menu-multiple dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'OdometerRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'OdometerRecord')">@translator.Translate(userLanguage, "Duplicate To Vehicle")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'OdometerRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
|
||||
<li><hr class="context-menu-odometer-adjustment dropdown-divider"></li>
|
||||
<li><a class="context-menu-odometer-adjustment dropdown-item" href="#" onclick="adjustRecordsOdometer(selectedRow, 'OdometerRecord')">@translator.Translate(userLanguage, "Adjust Odometer")</a></li>
|
||||
|
||||
46
Views/Vehicle/_PlanOrderSupplies.cshtml
Normal file
46
Views/Vehicle/_PlanOrderSupplies.cshtml
Normal file
@@ -0,0 +1,46 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@model List<SupplyAvailability>
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@translator.Translate(userLanguage, "Order Supplies")</h5>
|
||||
<button type="button" class="btn-close" onclick="hideOrderSupplyModal()" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@if (!Model.Any() || Model.Any(x => x.Missing))
|
||||
{
|
||||
<p class="lead">@translator.Translate(userLanguage, "Missing Supplies, Please Delete This Template and Recreate It.")</p>
|
||||
} else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-12" style="max-height:50vh; overflow-y:auto;">
|
||||
<table class="table table-hover">
|
||||
<thead class="sticky-top">
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-6 text-truncate">@translator.Translate(userLanguage, "Description")</th>
|
||||
<th scope="col" class="col-3 text-truncate">@translator.Translate(userLanguage, "Required")</th>
|
||||
<th scope="col" class="col-3 text-truncate">@translator.Translate(userLanguage, "In Stock")</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (SupplyAvailability supplyAvailability in Model)
|
||||
{
|
||||
<tr class="d-flex @(supplyAvailability.Insufficient ? "table-danger" : "")">
|
||||
<td class="col-6 text-truncate">@StaticHelper.TruncateStrings(supplyAvailability.Description)</td>
|
||||
<td class="col-3 text-truncate">@supplyAvailability.Required.ToString("N2")</td>
|
||||
<td class="col-3 text-truncate">@supplyAvailability.InStock.ToString("N2")</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="hideOrderSupplyModal()">@translator.Translate(userLanguage, "Cancel")</button>
|
||||
</div>
|
||||
@@ -20,7 +20,7 @@
|
||||
<label for="planRecordDescription">@translator.Translate(userLanguage, "Description")</label>
|
||||
<input type="text" id="planRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage, "Describe the Plan")" value="@Model.Description">
|
||||
<label for="planRecordCost">@translator.Translate(userLanguage, "Cost")</label>
|
||||
<input type="text" inputmode="decimal" id="planRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage, "Cost of the Plan")" value="@Model.Cost">
|
||||
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="planRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage, "Cost of the Plan")" value="@Model.Cost">
|
||||
@if (isNew)
|
||||
{
|
||||
@await Html.PartialAsync("_SupplyStore", "PlanRecord")
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<label for="planRecordDescription">@translator.Translate(userLanguage, "Description")</label>
|
||||
<input type="text" id="planRecordDescription" class="form-control" placeholder="@translator.Translate(userLanguage, "Describe the Plan")" value="@Model.Description">
|
||||
<label for="planRecordCost">@translator.Translate(userLanguage, "Cost")</label>
|
||||
<input type="text" inputmode="decimal" id="planRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage, "Cost of the Plan")" value="@Model.Cost">
|
||||
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="planRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage, "Cost of the Plan")" value="@Model.Cost">
|
||||
@await Html.PartialAsync("_SupplyStore", "PlanRecordTemplate")
|
||||
<label for="planRecordType">@translator.Translate(userLanguage, "Type")</label>
|
||||
<select class="form-select" id="planRecordType">
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
}
|
||||
@if (planRecordTemplate.Supplies.Any())
|
||||
{
|
||||
<i class="bi bi-shop ms-2"></i>
|
||||
<i class="bi bi-shop ms-2" style="cursor:pointer;"onclick="orderPlanSupplies(@planRecordTemplate.Id)"></i>
|
||||
}
|
||||
@if (planRecordTemplate.ImportMode == ImportMode.ServiceRecord)
|
||||
{
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<div class="col-12">
|
||||
<div class="row mt-2 showOnPrint">
|
||||
<div class="d-flex">
|
||||
<img src="/defaults/lubelogger_logo.png" />
|
||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row swimlane">
|
||||
@@ -109,4 +109,11 @@
|
||||
<div class="modal-content" id="planRecordTemplateModalContent">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" data-bs-focus="false" id="planRecordTemplateSupplyOrderModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content" id="planRecordTemplateSupplyOrderModalContent">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,10 +1,10 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
@using CarCareTracker.Helper
|
||||
@model List<ReminderRecord>
|
||||
@if (Model.Count() > 1)
|
||||
{
|
||||
@@ -22,7 +22,7 @@
|
||||
}
|
||||
} else
|
||||
{
|
||||
<!option value="0">No Recurring Reminders Found</!option>
|
||||
<!option value="0">@translator.Translate(userLanguage, "No Recurring Reminders Found")</!option>
|
||||
}
|
||||
</select>
|
||||
<div id="recurringMultipleReminders" style="display:none;">
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<div class="col-12">
|
||||
<div class="row mt-2 showOnPrint">
|
||||
<div class="d-flex">
|
||||
<img src="/defaults/lubelogger_logo.png" />
|
||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-hover">
|
||||
@@ -90,7 +90,7 @@
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-truncate @(hasRefresh ? "col-3 col-md-4" : "col-5")" data-record-type='cost'>@reminderRecord.Description</td>
|
||||
<td class="col-2 col-md-3 text-truncate">@CarCareTracker.Helper.StaticHelper.TruncateStrings(reminderRecord.Notes)</td>
|
||||
<td class="col-2 col-md-3 text-truncate">@StaticHelper.TruncateStrings(reminderRecord.Notes)</td>
|
||||
@if (hasRefresh)
|
||||
{
|
||||
<td class="col-2 col-md-1 text-truncate">
|
||||
@@ -105,6 +105,11 @@
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
<tr class="d-flex">
|
||||
<td class="col-12 showOnPrint lubelogger-report-banner">
|
||||
@StaticHelper.ReportNote
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -121,6 +126,7 @@
|
||||
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()">@translator.Translate(userLanguage, "Deselect All")</a></li>
|
||||
<li><hr class="context-menu-multiple dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'ReminderRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'ReminderRecord')">@translator.Translate(userLanguage, "Duplicate To Vehicle")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'ReminderRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -126,6 +126,12 @@
|
||||
<div class="d-grid">
|
||||
<button onclick="exportAttachments()" class="btn btn-secondary btn-md mt-1 mb-1">@translator.Translate(userLanguage, "Export Attachments")<i class="bi ms-2 bi-box-arrow-in-up-right"></i></button>
|
||||
</div>
|
||||
@if (Model.CustomWidgetsConfigured)
|
||||
{
|
||||
<div class="d-grid">
|
||||
<button onclick="loadCustomWidgets()" class="btn btn-secondary btn-md mt-1 mb-1">@translator.Translate(userLanguage, "Additional Widgets")<i class="bi ms-2 bi-box-arrow-in-up-right"></i></button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -153,6 +159,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (Model.CustomWidgetsConfigured)
|
||||
{
|
||||
<div class="modal fade" data-bs-focus="false" id="vehicleCustomWidgetsModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content" id="vehicleCustomWidgetsModalContent">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div id="vehicleHistoryReport" class="showOnPrint"></div>
|
||||
|
||||
<script>
|
||||
|
||||
26
Views/Vehicle/_ReportParameters.cshtml
Normal file
26
Views/Vehicle/_ReportParameters.cshtml
Normal file
@@ -0,0 +1,26 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
}
|
||||
@model ReportParameter
|
||||
<div id="columnSelector">
|
||||
<ul class="list-group">
|
||||
@foreach(string column in Model.VisibleColumns)
|
||||
{
|
||||
<li class="list-group-item text-start">
|
||||
<input class="form-check-input column-default" type="checkbox" value="@column" id="visibleColumn_@column" checked>
|
||||
<label class="form-check-label stretched-link" for="visibleColumn_@column">@(translator.Translate(userLanguage, column == nameof(GenericReportModel.DataType) ? "Type" : column))</label>
|
||||
</li>
|
||||
}
|
||||
@foreach(string extraField in Model.ExtraFields)
|
||||
{
|
||||
<li class="list-group-item text-start">
|
||||
<input class="form-check-input column-extrafield" type="checkbox" value="@extraField" id="extraField_@extraField">
|
||||
<label class="form-check-label stretched-link" for="extraField_@extraField">@extraField</label>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
2
Views/Vehicle/_ReportWidgets.cshtml
Normal file
2
Views/Vehicle/_ReportWidgets.cshtml
Normal file
@@ -0,0 +1,2 @@
|
||||
@model string
|
||||
@Html.Raw(Model)
|
||||
@@ -43,7 +43,7 @@
|
||||
</div>
|
||||
}
|
||||
<label for="serviceRecordCost">@translator.Translate(userLanguage,"Cost")</label>
|
||||
<input type="text" inputmode="decimal" id="serviceRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of the service")" value="@(isNew ? "" : Model.Cost)">
|
||||
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="serviceRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of the service")" value="@(isNew ? "" : Model.Cost)">
|
||||
@if (isNew)
|
||||
{
|
||||
@await Html.PartialAsync("_SupplyStore", "ServiceRecord")
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
<div class="col-12">
|
||||
<div class="row mt-2 showOnPrint">
|
||||
<div class="d-flex">
|
||||
<img src="/defaults/lubelogger_logo.png" />
|
||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-hover">
|
||||
@@ -126,14 +126,14 @@
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@(serviceRecord.Mileage == default ? "---" : serviceRecord.Mileage.ToString())</td>
|
||||
<td class="col-3 col-xl-4 flex-grow-1 flex-shrink-1 text-truncate" data-column="description">@serviceRecord.Description</td>
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@((hideZero && serviceRecord.Cost == default) ? "---" : serviceRecord.Cost.ToString("C"))</td>
|
||||
<td class="col-3 text-truncate flex-grow-1 flex-shrink-1" data-column="notes">@CarCareTracker.Helper.StaticHelper.TruncateStrings(serviceRecord.Notes)</td>
|
||||
<td class="col-3 text-truncate flex-grow-1 flex-shrink-1" data-column="notes">@StaticHelper.TruncateStrings(serviceRecord.Notes)</td>
|
||||
@foreach (string extraFieldColumn in extraFields)
|
||||
{
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">
|
||||
@{
|
||||
var extraFieldValue = serviceRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "";
|
||||
if (!string.IsNullOrWhiteSpace(extraFieldValue) && Uri.IsWellFormedUriString(extraFieldValue, UriKind.Absolute)){
|
||||
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@CarCareTracker.Helper.StaticHelper.TruncateStrings(extraFieldValue)</a>
|
||||
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@StaticHelper.TruncateStrings(extraFieldValue)</a>
|
||||
} else
|
||||
{
|
||||
@extraFieldValue
|
||||
@@ -143,6 +143,11 @@
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
<tr class="d-flex">
|
||||
<td class="col-12 showOnPrint lubelogger-report-banner">
|
||||
@StaticHelper.ReportNote
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -166,6 +171,7 @@
|
||||
<li><a class="dropdown-item" href="#" onclick="moveRecords(selectedRow, 'ServiceRecord', 'UpgradeRecord')">@translator.Translate(userLanguage, "Upgrades")</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'ServiceRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'ServiceRecord')">@translator.Translate(userLanguage, "Duplicate To Vehicle")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'ServiceRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
|
||||
<li><hr class="context-menu-active-multiple dropdown-divider"></li>
|
||||
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="getRecordsDeltaStats(selectedRow)">@translator.Translate(userLanguage, "Statistics")</a></li>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<div class="col-md-6 col-12">
|
||||
<label for="supplyRecordQuantity">@translator.Translate(userLanguage,"Quantity")</label>
|
||||
<div class="input-group">
|
||||
<input type="text" inputmode="decimal" id="supplyRecordQuantity" class="form-control" placeholder="@translator.Translate(userLanguage,"Quantity")" value="@(isNew ? "1" : Model.Quantity)">
|
||||
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="supplyRecordQuantity" class="form-control" placeholder="@translator.Translate(userLanguage,"Quantity")" value="@(isNew ? "" : Model.Quantity.ToString("N2"))">
|
||||
<div class="input-group-text">
|
||||
<button type="button" class="btn btn-sm zero-y-padding btn-primary" onclick="replenishSupplies()"><i class="bi bi-plus"></i></button>
|
||||
</div>
|
||||
@@ -40,7 +40,7 @@
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<label for="supplyRecordCost">@translator.Translate(userLanguage,"Cost")</label>
|
||||
<input type="text" inputmode="decimal" id="supplyRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost")" value="@(isNew ? "" : Model.Cost)">
|
||||
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="supplyRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost")" value="@(isNew ? "" : Model.Cost)">
|
||||
</div>
|
||||
</div>
|
||||
<label for="supplyRecordTag">@translator.Translate(userLanguage, "Tags(optional)")</label>
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
<div class="col-12">
|
||||
<div class="row mt-2 showOnPrint">
|
||||
<div class="d-flex">
|
||||
<img src="/defaults/lubelogger_logo.png" />
|
||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-hover">
|
||||
@@ -142,7 +142,7 @@
|
||||
<td class="col-2 flex-grow-1 col-xl-3 text-truncate" data-column="description">@supplyRecord.Description</td>
|
||||
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="quantity">@supplyRecord.Quantity</td>
|
||||
<td class="col-1 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@((hideZero && supplyRecord.Cost == default) ? "---" : supplyRecord.Cost.ToString("C"))</td>
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@CarCareTracker.Helper.StaticHelper.TruncateStrings(supplyRecord.Notes)</td>
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@StaticHelper.TruncateStrings(supplyRecord.Notes)</td>
|
||||
@foreach (string extraFieldColumn in extraFields)
|
||||
{
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">
|
||||
@@ -150,7 +150,7 @@
|
||||
var extraFieldValue = supplyRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "";
|
||||
if (!string.IsNullOrWhiteSpace(extraFieldValue) && Uri.IsWellFormedUriString(extraFieldValue, UriKind.Absolute))
|
||||
{
|
||||
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@CarCareTracker.Helper.StaticHelper.TruncateStrings(extraFieldValue)</a>
|
||||
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@StaticHelper.TruncateStrings(extraFieldValue)</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -161,6 +161,11 @@
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
<tr class="d-flex">
|
||||
<td class="col-12 showOnPrint lubelogger-report-banner">
|
||||
@StaticHelper.ReportNote
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -179,6 +184,7 @@
|
||||
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()">@translator.Translate(userLanguage, "Deselect All")</a></li>
|
||||
<li><hr class="context-menu-multiple dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'SupplyRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'SupplyRecord')">@translator.Translate(userLanguage, "Duplicate To Vehicle")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'SupplyRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
|
||||
</ul>
|
||||
@if (userColumnPreferences.Any())
|
||||
|
||||
@@ -29,11 +29,11 @@
|
||||
<thead class="sticky-top">
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-1"></th>
|
||||
<th scope="col" class="col-2">@translator.Translate(userLanguage,"Quantity")</th>
|
||||
<th scope="col" class="col-2">@translator.Translate(userLanguage, "In Stock")</th>
|
||||
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Part Number")</th>
|
||||
<th scope="col" class="col-3">@translator.Translate(userLanguage, "Description")</th>
|
||||
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Unit Cost")</th>
|
||||
<th scope="col" class="col-3 col-sm-2 text-truncate flex-grow">@translator.Translate(userLanguage,"Quantity")</th>
|
||||
<th scope="col" class="col-1 col-sm-2 text-truncate flex-shrink">@translator.Translate(userLanguage, "In Stock")</th>
|
||||
<th scope="col" class="col-2 text-truncate flex-shrink">@translator.Translate(userLanguage, "Part Number")</th>
|
||||
<th scope="col" class="col-3 text-truncate flex-shrink">@translator.Translate(userLanguage, "Description")</th>
|
||||
<th scope="col" class="col-2 text-truncate flex-shrink">@translator.Translate(userLanguage, "Unit Cost")</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -42,11 +42,11 @@
|
||||
var supplyUsage = Model.Usage.Where(x => x.SupplyId == supplyRecord.Id).SingleOrDefault();
|
||||
<tr class="d-flex" id="supplyRows" data-tags='@string.Join(" ", supplyRecord.Tags)'>
|
||||
<td class="col-1"><input class="form-check-input" type="checkbox" onchange="toggleQuantityFieldDisabled(this)" value="@supplyRecord.Id" @(supplyUsage == default ? "" : "checked")></td>
|
||||
<td class="col-2"><input type="text" inputmode="decimal" @(supplyUsage == default ? "disabled" : "") value="@(supplyUsage == default ? "" : supplyUsage.Quantity)" onchange="recalculateTotal()" class="form-control"></td>
|
||||
<td class="col-2 supplyquantity">@supplyRecord.Quantity</td>
|
||||
<td class="col-2 text-truncate">@StaticHelper.TruncateStrings(supplyRecord.PartNumber)</td>
|
||||
<td class="col-3 text-truncate">@StaticHelper.TruncateStrings(supplyRecord.Description)</td>
|
||||
<td class="col-2 supplyprice">@((supplyRecord.Quantity > 0 ? supplyRecord.Cost / supplyRecord.Quantity : 0).ToString("F"))</td>
|
||||
<td class="col-3 col-sm-2 flex-grow text-truncate supplyquantityinput"><input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" @(supplyUsage == default ? "disabled" : "") value="@(supplyUsage == default ? "" : supplyUsage.Quantity)" onchange="recalculateTotal()" class="form-control"></td>
|
||||
<td class="col-1 col-sm-2 flex-shrink text-truncate supplyquantity">@supplyRecord.Quantity</td>
|
||||
<td class="col-2 flex-shrink text-truncate">@StaticHelper.TruncateStrings(supplyRecord.PartNumber)</td>
|
||||
<td class="col-3 flex-shrink text-truncate">@StaticHelper.TruncateStrings(supplyRecord.Description)</td>
|
||||
<td class="col-2 flex-shrink text-truncate supplyprice">@((supplyRecord.Quantity > 0 ? supplyRecord.Cost / supplyRecord.Quantity : 0).ToString("F"))</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
@@ -88,15 +88,15 @@
|
||||
recalculateTotal();
|
||||
}
|
||||
function getTextFieldFromCheckBox(elem) {
|
||||
var textField = $(elem.parentElement.parentElement).find('.col-2 > input[type=text]')[0];
|
||||
var textField = $(elem.parentElement.parentElement).find('.supplyquantityinput input[type=text]')[0];
|
||||
return $(textField);
|
||||
}
|
||||
function getInStockFieldFromCheckBox(elem) {
|
||||
var textField = $(elem.parentElement.parentElement).find('.col-2.supplyquantity')[0];
|
||||
var textField = $(elem.parentElement.parentElement).find('.supplyquantity')[0];
|
||||
return $(textField);
|
||||
}
|
||||
function getPriceFieldFromCheckBox(elem) {
|
||||
var textField = $(elem.parentElement.parentElement).find('.col-2.supplyprice')[0];
|
||||
var textField = $(elem.parentElement.parentElement).find('.supplyprice')[0];
|
||||
return $(textField);
|
||||
}
|
||||
function getSuppliesAndQuantity() {
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
</div>
|
||||
}
|
||||
<label for="taxRecordCost">@translator.Translate(userLanguage,"Cost")</label>
|
||||
<input type="text" inputmode="decimal" id="taxRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of tax paid")" value="@(isNew? "" : Model.Cost)">
|
||||
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="taxRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of tax paid")" value="@(isNew? "" : Model.Cost)">
|
||||
<label for="taxRecordTag">@translator.Translate(userLanguage,"Tags(optional)")</label>
|
||||
<select multiple class="form-select" id="taxRecordTag">
|
||||
@foreach (string tag in Model.Tags)
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
<div class="col-12">
|
||||
<div class="row mt-2 showOnPrint">
|
||||
<div class="d-flex">
|
||||
<img src="/defaults/lubelogger_logo.png" />
|
||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-hover">
|
||||
@@ -118,7 +118,7 @@
|
||||
<td class="col-3 flex-grow-1 col-xl-1 text-truncate" data-column="date">@taxRecord.Date.ToShortDateString()</td>
|
||||
<td class="col-4 flex-grow-1 col-xl-6 text-truncate" data-column="description">@taxRecord.Description</td>
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@((hideZero && taxRecord.Cost == default) ? "---" : taxRecord.Cost.ToString("C"))</td>
|
||||
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@CarCareTracker.Helper.StaticHelper.TruncateStrings(taxRecord.Notes)</td>
|
||||
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@StaticHelper.TruncateStrings(taxRecord.Notes)</td>
|
||||
@foreach (string extraFieldColumn in extraFields)
|
||||
{
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">
|
||||
@@ -126,7 +126,7 @@
|
||||
var extraFieldValue = taxRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "";
|
||||
if (!string.IsNullOrWhiteSpace(extraFieldValue) && Uri.IsWellFormedUriString(extraFieldValue, UriKind.Absolute))
|
||||
{
|
||||
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@CarCareTracker.Helper.StaticHelper.TruncateStrings(extraFieldValue)</a>
|
||||
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@StaticHelper.TruncateStrings(extraFieldValue)</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -137,6 +137,11 @@
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
<tr class="d-flex">
|
||||
<td class="col-12 showOnPrint lubelogger-report-banner">
|
||||
@StaticHelper.ReportNote
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -155,6 +160,7 @@
|
||||
<li><a class="context-menu-multiple context-menu-deselect-all dropdown-item" href="#" onclick="clearSelectedRows()">@translator.Translate(userLanguage, "Deselect All")</a></li>
|
||||
<li><hr class="context-menu-multiple dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'TaxRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'TaxRecord')">@translator.Translate(userLanguage, "Duplicate To Vehicle")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'TaxRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
|
||||
</ul>
|
||||
@if (userColumnPreferences.Any())
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@using CarCareTracker.Helper
|
||||
@model UpgradeRecordInput
|
||||
@{
|
||||
var isNew = Model.Id == 0;
|
||||
@@ -43,7 +43,7 @@
|
||||
</div>
|
||||
}
|
||||
<label for="upgradeRecordCost">@translator.Translate(userLanguage, "Cost")</label>
|
||||
<input type="text" inputmode="decimal" id="upgradeRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of the upgrade/mods")" value="@(isNew ? "" : Model.Cost)">
|
||||
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="upgradeRecordCost" class="form-control" placeholder="@translator.Translate(userLanguage,"Cost of the upgrade/mods")" value="@(isNew ? "" : Model.Cost)">
|
||||
@if (isNew)
|
||||
{
|
||||
@await Html.PartialAsync("_SupplyStore", "UpgradeRecord")
|
||||
@@ -87,7 +87,7 @@
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
<label for="upgradeRecordFiles">Upload documents(optional)</label>
|
||||
<label for="upgradeRecordFiles">@translator.Translate(userLanguage,"Upload documents(optional)")</label>
|
||||
<input onChange="uploadVehicleFilesAsync(this)" type="file" multiple accept="@config.GetAllowedFileUploadExtensions()" class="form-control-file" id="upgradeRecordFiles">
|
||||
<br /><small class="text-body-secondary">@translator.Translate(userLanguage, "Max File Size: 28.6MB")</small>
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
<div class="col-12">
|
||||
<div class="row mt-2 showOnPrint">
|
||||
<div class="d-flex">
|
||||
<img src="/defaults/lubelogger_logo.png" />
|
||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-hover">
|
||||
@@ -126,7 +126,7 @@
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="odometer">@(upgradeRecord.Mileage == default ? "---" : upgradeRecord.Mileage.ToString())</td>
|
||||
<td class="col-3 flex-grow-1 flex-shrink-1 col-xl-4 text-truncate" data-column="description">@upgradeRecord.Description</td>
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" data-column="cost" data-record-type="cost">@((hideZero && upgradeRecord.Cost == default) ? "---" : upgradeRecord.Cost.ToString("C"))</td>
|
||||
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@CarCareTracker.Helper.StaticHelper.TruncateStrings(upgradeRecord.Notes)</td>
|
||||
<td class="col-3 flex-grow-1 flex-shrink-1 text-truncate" data-column="notes">@StaticHelper.TruncateStrings(upgradeRecord.Notes)</td>
|
||||
@foreach (string extraFieldColumn in extraFields)
|
||||
{
|
||||
<td class="col-2 flex-grow-1 flex-shrink-1 text-truncate" style='display:none;' data-column="@extraFieldColumn">
|
||||
@@ -134,7 +134,7 @@
|
||||
var extraFieldValue = upgradeRecord.ExtraFields.Where(x => x.Name == extraFieldColumn)?.FirstOrDefault()?.Value ?? "";
|
||||
if (!string.IsNullOrWhiteSpace(extraFieldValue) && Uri.IsWellFormedUriString(extraFieldValue, UriKind.Absolute))
|
||||
{
|
||||
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@CarCareTracker.Helper.StaticHelper.TruncateStrings(extraFieldValue)</a>
|
||||
<a href="@extraFieldValue" onclick="noPropagation()" target="_blank">@StaticHelper.TruncateStrings(extraFieldValue)</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -145,6 +145,11 @@
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
<tr class="d-flex">
|
||||
<td class="col-12 showOnPrint lubelogger-report-banner">
|
||||
@StaticHelper.ReportNote
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -167,6 +172,7 @@
|
||||
<li><a class="dropdown-item" href="#" onclick="moveRecords(selectedRow, 'UpgradeRecord', 'RepairRecord')">@translator.Translate(userLanguage, "Repairs")</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecords(selectedRow, 'UpgradeRecord')">@translator.Translate(userLanguage, "Duplicate")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="duplicateRecordsToOtherVehicles(selectedRow, 'UpgradeRecord')">@translator.Translate(userLanguage, "Duplicate To Vehicle")</a></li>
|
||||
<li><a class="dropdown-item" href="#" onclick="deleteRecords(selectedRow, 'UpgradeRecord')">@translator.Translate(userLanguage, "Delete")</a></li>
|
||||
<li><hr class="context-menu-active-multiple dropdown-divider"></li>
|
||||
<li><a class="context-menu-active-multiple dropdown-item" href="#" onclick="getRecordsDeltaStats(selectedRow)">@translator.Translate(userLanguage, "Statistics")</a></li>
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
@using CarCareTracker.Helper
|
||||
@inject IConfigHelper config
|
||||
@inject ITranslationHelper translator
|
||||
@model VehicleHistoryViewModel
|
||||
@{
|
||||
var userConfig = config.GetUserConfig(User);
|
||||
var hideZero = userConfig.HideZero;
|
||||
var userLanguage = userConfig.UserLanguage;
|
||||
var extraFields = Model.ReportParameters.ExtraFields;
|
||||
}
|
||||
@model VehicleHistoryViewModel
|
||||
<div class="vehicleDetailTabContainer">
|
||||
<div class="row mt-2">
|
||||
<div class="d-flex">
|
||||
<img src="/defaults/lubelogger_logo.png" />
|
||||
<img src="@config.GetLogoUrl()" class="lubelogger-logo" />
|
||||
<span class="display-6 ms-5">@translator.Translate(userLanguage, "Vehicle Maintenance Report")</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -107,19 +108,23 @@
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-2 servicehistorytype">@translator.Translate(userLanguage, "Type")</th>
|
||||
<th scope="col" class="col-1 servicehistorydate">@translator.Translate(userLanguage, "Date")</th>
|
||||
<th scope="col" class="col-1">@translator.Translate(userLanguage, "Odometer")</th>
|
||||
<th scope="col" class="col-3">@translator.Translate(userLanguage, "Description")</th>
|
||||
<th scope="col" class="col-1">@translator.Translate(userLanguage, "Cost")</th>
|
||||
<th scope="col" class="col-4">@translator.Translate(userLanguage, "Notes")</th>
|
||||
<th scope="col" class="col-2 text-truncate flex-grow-1 flex-shrink-1 @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.DataType)) ? "" : "d-none")">@translator.Translate(userLanguage, "Type")</th>
|
||||
<th scope="col" class="col-2 text-truncate flex-grow-1 flex-shrink-1 @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.Date)) ? "" : "d-none")">@translator.Translate(userLanguage, "Date")</th>
|
||||
<th scope="col" class="col-2 text-truncate flex-grow-1 flex-shrink-1 @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.Odometer)) ? "" : "d-none")">@translator.Translate(userLanguage, "Odometer")</th>
|
||||
<th scope="col" class="col-3 text-truncate flex-grow-1 flex-shrink-1 @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.Description)) ? "" : "d-none")">@translator.Translate(userLanguage, "Description")</th>
|
||||
<th scope="col" class="col-2 text-truncate flex-grow-1 flex-shrink-1 @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.Cost)) ? "" : "d-none")">@translator.Translate(userLanguage, "Cost")</th>
|
||||
<th scope="col" class="col-4 text-truncate flex-grow-1 flex-shrink-1 @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.Notes)) ? "" : "d-none")">@translator.Translate(userLanguage, "Notes")</th>
|
||||
@foreach(string extraField in extraFields)
|
||||
{
|
||||
<th scope="col" class="col-2 text-truncate flex-grow-1 flex-shrink-1">@extraField</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (GenericReportModel reportData in Model.VehicleHistory)
|
||||
{
|
||||
<tr class="d-flex">
|
||||
<td class="col-2 servicehistorytype">
|
||||
<td class="col-2 text-truncate flex-grow-1 flex-shrink-1 @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.DataType)) ? "" : "d-none")">
|
||||
@if (reportData.DataType == ImportMode.ServiceRecord)
|
||||
{
|
||||
<span><i class="bi bi-card-checklist me-2"></i>@translator.Translate(userLanguage, "Service")</span>
|
||||
@@ -137,13 +142,22 @@
|
||||
<span><i class="bi bi-currency-dollar me-2"></i>@translator.Translate(userLanguage, "Tax")</span>
|
||||
}
|
||||
</td>
|
||||
<td class="col-1 servicehistorydate">@reportData.Date.ToShortDateString()</td>
|
||||
<td class="col-1">@(reportData.Odometer == default ? "---" : reportData.Odometer.ToString("N0"))</td>
|
||||
<td class="col-3">@reportData.Description</td>
|
||||
<td class="col-1">@((hideZero && reportData.Cost == default) ? "---" : reportData.Cost.ToString("C"))</td>
|
||||
<td class="col-4 text-wrap">@CarCareTracker.Helper.StaticHelper.TruncateStrings(reportData.Notes, 100)</td>
|
||||
<td class="col-2 text-truncate flex-grow-1 flex-shrink-1 @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.Date)) ? "" : "d-none")">@reportData.Date.ToShortDateString()</td>
|
||||
<td class="col-2 text-truncate flex-grow-1 flex-shrink-1 @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.Odometer)) ? "" : "d-none")">@(reportData.Odometer == default ? "---" : reportData.Odometer.ToString("N0"))</td>
|
||||
<td class="col-3 text-truncate flex-grow-1 flex-shrink-1 @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.Description)) ? "" : "d-none")">@reportData.Description</td>
|
||||
<td class="col-2 text-truncate flex-grow-1 flex-shrink-1 @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.Cost)) ? "" : "d-none")">@((hideZero && reportData.Cost == default) ? "---" : reportData.Cost.ToString("C"))</td>
|
||||
<td class="col-4 flex-grow-1 flex-shrink-1 text-wrap text-break @(Model.ReportParameters.VisibleColumns.Contains(nameof(GenericReportModel.Notes)) ? "" : "d-none")">@StaticHelper.TruncateStrings(reportData.Notes, 100)</td>
|
||||
@foreach(string extraField in extraFields)
|
||||
{
|
||||
<td class="col-2 text-truncate flex-grow-1 flex-shrink-1">@(reportData.ExtraFields.Where(x => x.Name == extraField)?.FirstOrDefault()?.Value ?? "")</td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
<tr class="d-flex">
|
||||
<td class="col-12 showOnPrint lubelogger-report-banner">
|
||||
@StaticHelper.ReportNote
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -93,9 +93,9 @@
|
||||
<label for="inputSoldDate">@translator.Translate(userLanguage, "Sold Date(optional)")</label>
|
||||
<input type="text" id="inputSoldDate" class="form-control" placeholder="@translator.Translate(userLanguage, "Sold Date")" value="@Model.SoldDate">
|
||||
<label for="inputPurchasePrice">@translator.Translate(userLanguage, "Purchased Price(optional)")</label>
|
||||
<input type="text" inputmode="decimal" id="inputPurchasePrice" class="form-control" placeholder="@translator.Translate(userLanguage, "Purchased Price")" value="@(Model.PurchasePrice == default ? "" : Model.PurchasePrice)">
|
||||
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="inputPurchasePrice" class="form-control" placeholder="@translator.Translate(userLanguage, "Purchased Price")" value="@(Model.PurchasePrice == default ? "" : Model.PurchasePrice)">
|
||||
<label for="inputSoldPrice">@translator.Translate(userLanguage, "Sold Price(optional)")</label>
|
||||
<input type="text" inputmode="decimal" id="inputSoldPrice" class="form-control" placeholder="@translator.Translate(userLanguage, "Sold Price")" value="@(Model.SoldPrice == default ? "" : Model.SoldPrice)">
|
||||
<input type="text" inputmode="decimal" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)" id="inputSoldPrice" class="form-control" placeholder="@translator.Translate(userLanguage, "Sold Price")" value="@(Model.SoldPrice == default ? "" : Model.SoldPrice)">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"DisableRegistration": false,
|
||||
"EnableRootUserOIDC": false,
|
||||
"HideZero": false,
|
||||
"AutomaticDecimalFormat": false,
|
||||
"EnableAutoReminderRefresh": false,
|
||||
"EnableAutoOdometerInsert": false,
|
||||
"EnableShopSupplies": false,
|
||||
|
||||
@@ -107,25 +107,6 @@ html {
|
||||
background-color: #fff !important;
|
||||
}
|
||||
|
||||
td.col-1 {
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
td.col-4.text-wrap {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.col-2.servicehistorytype {
|
||||
width: 13%;
|
||||
}
|
||||
.col-1.servicehistorydate{
|
||||
width: 13%;
|
||||
}
|
||||
|
||||
th.col-1 {
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
th {
|
||||
color: #000 !important;
|
||||
background-color: #fff !important;
|
||||
@@ -467,6 +448,18 @@ html[data-bs-theme="light"] .api-method:hover {
|
||||
padding: 0rem;
|
||||
}
|
||||
|
||||
.lubelogger-logo {
|
||||
height: 48px;
|
||||
width: 204px;
|
||||
object-fit: scale-down;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
::-ms-reveal {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.lubelogger-report-banner {
|
||||
border-top: thin solid black;
|
||||
text-align: center;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -150,6 +150,9 @@ function usePlannerRecordTemplate(planRecordTemplateId) {
|
||||
saveScrollPosition();
|
||||
getVehiclePlanRecords(vehicleId);
|
||||
} else {
|
||||
if (data.message == "Insufficient Supplies") {
|
||||
data.message += `<br /><br /><a class='text-link' style='cursor:pointer;' onclick='orderPlanSupplies(${planRecordTemplateId}, true)'>Order Required Supplies</a>`
|
||||
}
|
||||
errorToast(data.message);
|
||||
}
|
||||
});
|
||||
@@ -325,4 +328,24 @@ function updatePlanRecordProgress(newProgress) {
|
||||
draggedId = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
function orderPlanSupplies(planRecordTemplateId, closeSwal) {
|
||||
if (closeSwal) {
|
||||
Swal.close();
|
||||
}
|
||||
$.get(`/Vehicle/OrderPlanSupplies?planRecordTemplateId=${planRecordTemplateId}`, function (data) {
|
||||
if (data.success != undefined && !data.success) {
|
||||
//success is provided.
|
||||
errorToast(data.message);
|
||||
} else {
|
||||
//hide plan record template modal.
|
||||
hidePlanRecordTemplatesModal();
|
||||
$("#planRecordTemplateSupplyOrderModalContent").html(data);
|
||||
$("#planRecordTemplateSupplyOrderModal").modal('show');
|
||||
}
|
||||
})
|
||||
}
|
||||
function hideOrderSupplyModal() {
|
||||
$("#planRecordTemplateSupplyOrderModal").modal('hide');
|
||||
showPlanRecordTemplatesModal();
|
||||
}
|
||||
@@ -1,14 +1,64 @@
|
||||
function getYear() {
|
||||
return $("#yearOption").val() ?? '0';
|
||||
}
|
||||
function getAndValidateSelectedColumns() {
|
||||
var reportVisibleColumns = [];
|
||||
var reportExtraFields = [];
|
||||
$("#columnSelector :checked").map(function () {
|
||||
if ($(this).hasClass('column-default')) {
|
||||
reportVisibleColumns.push(this.value);
|
||||
} else {
|
||||
reportExtraFields.push(this.value);
|
||||
}
|
||||
});
|
||||
if (reportVisibleColumns.length + reportExtraFields.length == 0) {
|
||||
return {
|
||||
hasError: true,
|
||||
visibleColumns: [],
|
||||
extraFields: []
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
hasError: false,
|
||||
visibleColumns: reportVisibleColumns,
|
||||
extraFields: reportExtraFields
|
||||
}
|
||||
}
|
||||
}
|
||||
function generateVehicleHistoryReport() {
|
||||
var vehicleId = GetVehicleId().vehicleId;
|
||||
$.get(`/Vehicle/GetVehicleHistory?vehicleId=${vehicleId}`, function (data) {
|
||||
$.get(`/Vehicle/GetReportParameters`, function (data) {
|
||||
if (data) {
|
||||
$("#vehicleHistoryReport").html(data);
|
||||
setTimeout(function () {
|
||||
window.print();
|
||||
}, 500);
|
||||
//prompt user to select a vehicle
|
||||
Swal.fire({
|
||||
title: 'Select Columns',
|
||||
html: data,
|
||||
confirmButtonText: 'Generate Report',
|
||||
focusConfirm: false,
|
||||
preConfirm: () => {
|
||||
//validate
|
||||
var selectedColumnsData = getAndValidateSelectedColumns();
|
||||
if (selectedColumnsData.hasError) {
|
||||
Swal.showValidationMessage(`You must select at least one column`);
|
||||
}
|
||||
return { selectedColumnsData }
|
||||
},
|
||||
}).then(function (result) {
|
||||
if (result.isConfirmed) {
|
||||
var vehicleId = GetVehicleId().vehicleId;
|
||||
$.post(`/Vehicle/GetVehicleHistory?vehicleId=${vehicleId}`, {
|
||||
reportParameter: result.value.selectedColumnsData
|
||||
}, function (data) {
|
||||
if (data) {
|
||||
$("#vehicleHistoryReport").html(data);
|
||||
setTimeout(function () {
|
||||
window.print();
|
||||
}, 500);
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
} else {
|
||||
errorToast(genericErrorMessage());
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -264,4 +314,13 @@ function loadGlobalSearchResult(recordId, recordType) {
|
||||
waitForElement('#planRecordModalContent', showEditPlanRecordModal, recordId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
function loadCustomWidgets() {
|
||||
$.get('/Vehicle/GetAdditionalWidgets', function (data) {
|
||||
$("#vehicleCustomWidgetsModalContent").html(data);
|
||||
$("#vehicleCustomWidgetsModal").modal('show');
|
||||
})
|
||||
}
|
||||
function hideCustomWidgetsModal() {
|
||||
$("#vehicleCustomWidgetsModal").modal('hide');
|
||||
}
|
||||
@@ -54,6 +54,7 @@ function updateSettings() {
|
||||
useMPG: $("#useMPG").is(':checked'),
|
||||
useDescending: $("#useDescending").is(':checked'),
|
||||
hideZero: $("#hideZero").is(":checked"),
|
||||
automaticDecimalFormat: $("#automaticDecimalFormat").is(":checked"),
|
||||
useUKMpg: $("#useUKMPG").is(":checked"),
|
||||
useThreeDecimalGasCost: $("#useThreeDecimal").is(":checked"),
|
||||
useMarkDownOnSavedNotes: $("#useMarkDownOnSavedNotes").is(":checked"),
|
||||
@@ -188,6 +189,7 @@ function saveTranslation() {
|
||||
errorToast(genericErrorMessage());
|
||||
return;
|
||||
}
|
||||
var userCanDelete = $(".translation-delete").length > 0;
|
||||
Swal.fire({
|
||||
title: 'Save Translation',
|
||||
html: `
|
||||
@@ -199,7 +201,7 @@ function saveTranslation() {
|
||||
const translationFileName = $("#translationFileName").val();
|
||||
if (!translationFileName || translationFileName.trim() == '') {
|
||||
Swal.showValidationMessage(`Please enter a valid file name`);
|
||||
} else if (translationFileName.trim() == 'en_US') {
|
||||
} else if (translationFileName.trim() == 'en_US' && !userCanDelete) {
|
||||
Swal.showValidationMessage(`en_US is reserved, please enter a different name`);
|
||||
}
|
||||
return { translationFileName }
|
||||
@@ -269,6 +271,9 @@ function downloadAllTranslations() {
|
||||
}
|
||||
})
|
||||
}
|
||||
function deleteTranslationKey(e) {
|
||||
$(e).parent().parent().remove();
|
||||
}
|
||||
//tabs reorder
|
||||
function showTabReorderModal() {
|
||||
//reorder the list items based on the CSS attribute
|
||||
@@ -350,4 +355,63 @@ function resetTabOrder() {
|
||||
$(elem).css('order', -1);
|
||||
})
|
||||
updateSettings();
|
||||
}
|
||||
|
||||
function hideCustomWidgets() {
|
||||
$("#customWidgetModal").modal('hide');
|
||||
}
|
||||
function saveCustomWidgets() {
|
||||
$.post('/Home/SaveCustomWidgets', { widgetsData: $("#widgetEditor").val() }, function (data) {
|
||||
if (data) {
|
||||
successToast("Custom Widgets Saved!");
|
||||
updateSettings();
|
||||
} else {
|
||||
errorToast(genericErrorMessage());
|
||||
}
|
||||
})
|
||||
}
|
||||
function deleteCustomWidgets() {
|
||||
$.post('/Home/DeleteCustomWidgets', function (data) {
|
||||
if (data) {
|
||||
successToast("Custom Widgets Deleted!");
|
||||
updateSettings();
|
||||
} else {
|
||||
errorToast(genericErrorMessage());
|
||||
}
|
||||
})
|
||||
}
|
||||
function showCustomWidgets() {
|
||||
Swal.fire({
|
||||
title: 'Warning',
|
||||
icon: "warning",
|
||||
html: `
|
||||
<span>
|
||||
You are about to use the Custom Widgets Editor, this is a developer-focused feature that can lead to security vulnerabilities if you don't understand what you're doing.
|
||||
<br />Zero support will be provided from the developer(s) of LubeLogger regarding Custom Widgets, Read the Documentation.
|
||||
<br />By proceeding, you acknowledge that you are solely responsible for all consequences from utilizing the Custom Widgets Editor.
|
||||
<br />To proceed, enter 'acknowledge' into the text field below.
|
||||
</span>
|
||||
<input type="text" id="inputAcknowledge" class="swal2-input" placeholder="acknowledge" onkeydown="handleSwalEnter(event)">
|
||||
`,
|
||||
confirmButtonText: 'Proceed',
|
||||
focusConfirm: false,
|
||||
preConfirm: () => {
|
||||
const userAcknowledge = $("#inputAcknowledge").val();
|
||||
if (!userAcknowledge || userAcknowledge != 'acknowledge') {
|
||||
Swal.showValidationMessage(`Please acknowledge before proceeding.`)
|
||||
}
|
||||
return { userAcknowledge }
|
||||
},
|
||||
}).then(function (result) {
|
||||
if (result.isConfirmed) {
|
||||
$.get('/Home/GetCustomWidgetEditor', function (data) {
|
||||
if (data.trim() != '') {
|
||||
$("#customWidgetModalContent").html(data);
|
||||
$("#customWidgetModal").modal('show');
|
||||
} else {
|
||||
errorToast("Custom Widgets Not Enabled");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -849,6 +849,85 @@ function duplicateRecords(ids, source) {
|
||||
}
|
||||
});
|
||||
}
|
||||
function duplicateRecordsToOtherVehicles(ids, source) {
|
||||
if (ids.length == 0) {
|
||||
return;
|
||||
}
|
||||
$("#workAroundInput").show();
|
||||
var friendlySource = "";
|
||||
var refreshDataCallBack;
|
||||
var recordVerbiage = ids.length > 1 ? `these ${ids.length} records` : "this record";
|
||||
switch (source) {
|
||||
case "ServiceRecord":
|
||||
friendlySource = "Service Records";
|
||||
refreshDataCallBack = getVehicleServiceRecords;
|
||||
break;
|
||||
case "RepairRecord":
|
||||
friendlySource = "Repairs";
|
||||
refreshDataCallBack = getVehicleCollisionRecords;
|
||||
break;
|
||||
case "UpgradeRecord":
|
||||
friendlySource = "Upgrades";
|
||||
refreshDataCallBack = getVehicleUpgradeRecords;
|
||||
break;
|
||||
case "TaxRecord":
|
||||
friendlySource = "Taxes";
|
||||
refreshDataCallBack = getVehicleTaxRecords;
|
||||
break;
|
||||
case "SupplyRecord":
|
||||
friendlySource = "Supplies";
|
||||
refreshDataCallBack = getVehicleSupplyRecords;
|
||||
break;
|
||||
case "NoteRecord":
|
||||
friendlySource = "Notes";
|
||||
refreshDataCallBack = getVehicleNotes;
|
||||
break;
|
||||
case "OdometerRecord":
|
||||
friendlySource = "Odometer Records";
|
||||
refreshDataCallBack = getVehicleOdometerRecords;
|
||||
break;
|
||||
case "ReminderRecord":
|
||||
friendlySource = "Reminders";
|
||||
refreshDataCallBack = getVehicleReminders;
|
||||
break;
|
||||
case "GasRecord":
|
||||
friendlySource = "Fuel Records";
|
||||
refreshDataCallBack = getVehicleGasRecords;
|
||||
break;
|
||||
}
|
||||
|
||||
$.get(`/Home/GetVehicleSelector?vehicleId=${GetVehicleId().vehicleId}`, function (data) {
|
||||
if (data) {
|
||||
//prompt user to select a vehicle
|
||||
Swal.fire({
|
||||
title: 'Duplicate to Vehicle(s)',
|
||||
html: data,
|
||||
confirmButtonText: 'Duplicate',
|
||||
focusConfirm: false,
|
||||
preConfirm: () => {
|
||||
//validate
|
||||
var selectedVehicleData = getAndValidateSelectedVehicle();
|
||||
if (selectedVehicleData.hasError) {
|
||||
Swal.showValidationMessage(`You must select a vehicle`);
|
||||
}
|
||||
return { selectedVehicleData }
|
||||
},
|
||||
}).then(function (result) {
|
||||
if (result.isConfirmed) {
|
||||
$.post('/Vehicle/DuplicateRecordsToOtherVehicles', { recordIds: ids, vehicleIds: result.value.selectedVehicleData.ids, importMode: source}, function (data) {
|
||||
if (data) {
|
||||
successToast(`${ids.length} Record(s) Duplicated`);
|
||||
} else {
|
||||
errorToast(genericErrorMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
errorToast(genericErrorMessage());
|
||||
}
|
||||
})
|
||||
}
|
||||
var selectedRow = [];
|
||||
var isDragging = false;
|
||||
$(window).on('mouseup', function (e) {
|
||||
@@ -1056,13 +1135,17 @@ function detectRowTouchEndPremature(sender) {
|
||||
rowTouchTimer = null;
|
||||
}
|
||||
}
|
||||
function handleSupplyAddCostKeyDown(event) {
|
||||
handleSwalEnter(event);
|
||||
interceptDecimalKeys(event);
|
||||
}
|
||||
function replenishSupplies() {
|
||||
Swal.fire({
|
||||
title: 'Replenish Supplies',
|
||||
html: `
|
||||
<input type="text" id="inputSupplyAddQuantity" class="swal2-input" placeholder="Quantity">
|
||||
<input type="text" id="inputSupplyAddQuantity" class="swal2-input" placeholder="Quantity" onkeydown="interceptDecimalKeys(event)" onkeyup="fixDecimalInput(this, 2)">
|
||||
<br />
|
||||
<input type="text" id="inputSupplyAddCost" class="swal2-input" placeholder="Cost" onkeydown="handleSwalEnter(event)">
|
||||
<input type="text" id="inputSupplyAddCost" class="swal2-input" placeholder="Cost" onkeydown="handleSupplyAddCostKeyDown(event)" onkeyup="fixDecimalInput(this, 2)">
|
||||
<br />
|
||||
<span class='small'>leave blank to use unit cost calculation</span>
|
||||
`,
|
||||
|
||||
@@ -572,6 +572,23 @@ function showMultipleRemindersSelector() {
|
||||
$("#recurringReminderInput").show();
|
||||
}
|
||||
}
|
||||
function getAndValidateSelectedVehicle() {
|
||||
var selectedVehiclesArray = [];
|
||||
$("#vehicleSelector :checked").map(function () {
|
||||
selectedVehiclesArray.push(this.value);
|
||||
});
|
||||
if (selectedVehiclesArray.length == 0) {
|
||||
return {
|
||||
hasError: true,
|
||||
ids: []
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
hasError: false,
|
||||
ids: selectedVehiclesArray
|
||||
}
|
||||
}
|
||||
}
|
||||
function getAndValidateSelectedRecurringReminder() {
|
||||
if ($("#multipleRemindersCheck").is(":checked")) {
|
||||
//validate multiple reminders
|
||||
|
||||
9
wwwroot/lib/masonry/masonry.min.js
vendored
Normal file
9
wwwroot/lib/masonry/masonry.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user