Merge branch 'main' into Hargata/postgres

This commit is contained in:
DESKTOP-GENO133\IvanPlex
2024-02-08 17:50:30 -07:00
11 changed files with 198 additions and 120 deletions

View File

@@ -161,10 +161,6 @@ namespace CarCareTracker.Controllers
[HttpGet]
public IActionResult ExportFromVehicleToCsv(int vehicleId, ImportMode mode)
{
if (vehicleId == default)
{
return Json(false);
}
string uploadDirectory = "temp/";
string uploadPath = Path.Combine(_webEnv.WebRootPath, uploadDirectory);
if (!Directory.Exists(uploadPath))
@@ -349,7 +345,7 @@ namespace CarCareTracker.Controllers
[HttpPost]
public IActionResult ImportToVehicleIdFromCsv(int vehicleId, ImportMode mode, string fileName)
{
if (vehicleId == default || string.IsNullOrWhiteSpace(fileName))
if (string.IsNullOrWhiteSpace(fileName))
{
return Json(false);
}
@@ -1522,6 +1518,10 @@ namespace CarCareTracker.Controllers
public IActionResult GetSupplyRecordsForRecordsByVehicleId(int vehicleId)
{
var result = _supplyRecordDataAccess.GetSupplyRecordsByVehicleId(vehicleId);
if (_config.GetServerEnableShopSupplies())
{
result.AddRange(_supplyRecordDataAccess.GetSupplyRecordsByVehicleId(0)); // add shop supplies
}
result.RemoveAll(x => x.Quantity <= 0);
bool _useDescending = _config.GetUserConfig(User).UseDescending;
if (_useDescending)

View File

@@ -1,4 +1,5 @@
using CarCareTracker.Logic;
using CarCareTracker.Helper;
using CarCareTracker.Logic;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
@@ -9,17 +10,31 @@ namespace CarCareTracker.Filter
public class CollaboratorFilter: ActionFilterAttribute
{
private readonly IUserLogic _userLogic;
public CollaboratorFilter(IUserLogic userLogic) {
private readonly IConfigHelper _config;
public CollaboratorFilter(IUserLogic userLogic, IConfigHelper config) {
_userLogic = userLogic;
_config = config;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var shopSupplyEndpoints = new List<string> { "ImportToVehicleIdFromCsv", "GetSupplyRecordsByVehicleId", "ExportFromVehicleToCsv" };
if (!filterContext.HttpContext.User.IsInRole(nameof(UserData.IsRootUser)))
{
var vehicleId = int.Parse(filterContext.ActionArguments["vehicleId"].ToString());
var userId = int.Parse(filterContext.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier));
if (!_userLogic.UserCanEditVehicle(userId, vehicleId))
if (vehicleId != default)
{
var userId = int.Parse(filterContext.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier));
if (!_userLogic.UserCanEditVehicle(userId, vehicleId))
{
filterContext.Result = new RedirectResult("/Error/Unauthorized");
}
} else if (shopSupplyEndpoints.Contains(filterContext.RouteData.Values["action"].ToString()) && !_config.GetServerEnableShopSupplies())
{
//user trying to access shop supplies but shop supplies is not enabled by root user.
filterContext.Result = new RedirectResult("/Error/Unauthorized");
} else if (!shopSupplyEndpoints.Contains(filterContext.RouteData.Values["action"].ToString()))
{
//user trying to access any other endpoints using 0 as vehicle id.
filterContext.Result = new RedirectResult("/Error/Unauthorized");
}
}

View File

@@ -11,6 +11,7 @@ namespace CarCareTracker.Helper
bool SaveUserConfig(ClaimsPrincipal user, UserConfig configData);
string GetLogoUrl();
string GetServerLanguage();
bool GetServerEnableShopSupplies();
public bool DeleteUserConfig(int userId);
}
public class ConfigHelper : IConfigHelper
@@ -40,6 +41,10 @@ namespace CarCareTracker.Helper
var serverLanguage = _config[nameof(UserConfig.UserLanguage)] ?? "en_US";
return serverLanguage;
}
public bool GetServerEnableShopSupplies()
{
return bool.Parse(_config[nameof(UserConfig.EnableShopSupplies)] ?? "false");
}
public bool SaveUserConfig(ClaimsPrincipal user, UserConfig configData)
{
var storedUserId = user.FindFirstValue(ClaimTypes.NameIdentifier);

View File

@@ -13,6 +13,7 @@
public bool UseMarkDownOnSavedNotes { get; set; }
public bool EnableAutoReminderRefresh { get; set; }
public bool EnableAutoOdometerInsert { get; set; }
public bool EnableShopSupplies { get; set; }
public string PreferredGasUnit { get; set; } = string.Empty;
public string PreferredGasMileageUnit { get; set; } = string.Empty;
public string UserNameHash { get; set; }

View File

@@ -13,12 +13,20 @@
}
@section Scripts {
<script src="~/js/garage.js"></script>
<script src="~/js/supplyrecord.js"></script>
<script src="~/lib/drawdown/drawdown.js"></script>
}
<div class="lubelogger-mobile-nav" onclick="hideMobileNav()">
<ul class="navbar-nav" id="homeTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link user-select-none @(Model == "garage" ? "active" : "")" ontouchstart="detectLongTouch(this)" ontouchend="detectTouchEndPremature(this)" id="garage-tab" data-bs-toggle="tab" data-bs-target="#garage-tab-pane" type="button" role="tab"><span class="ms-2 display-3"><i class="bi bi-car-front me-2"></i>@translator.Translate(userLanguage,"Garage")</span></button>
</li>
@if(config.GetServerEnableShopSupplies())
{
<li class="nav-item" role="presentation">
<button class="nav-link" id="supply-tab" data-bs-toggle="tab" data-bs-target="#supply-tab-pane" type="button" role="tab"><span class="ms-2 display-3"><i class="bi bi-shop me-2"></i>@translator.Translate(userLanguage, "Supplies")</button>
</li>
}
<li class="nav-item" role="presentation">
<button class="nav-link @(Model == "settings" ? "active" : "")" id="settings-tab" data-bs-toggle="tab" data-bs-target="#settings-tab-pane" type="button" role="tab"><span class="ms-2 display-3"><i class="bi bi-gear me-2"></i>@translator.Translate(userLanguage,"Settings")</span></button>
</li>
@@ -50,6 +58,12 @@
<li class="nav-item" role="presentation">
<button class="nav-link @(Model == "garage" ? "active" : "")" oncontextmenu="sortGarage(this)" id="garage-tab" data-bs-toggle="tab" data-bs-target="#garage-tab-pane" type="button" role="tab"><i class="bi bi-car-front me-2"></i>@translator.Translate(userLanguage,"Garage")</button>
</li>
@if (config.GetServerEnableShopSupplies())
{
<li class="nav-item" role="presentation">
<button class="nav-link" id="supply-tab" data-bs-toggle="tab" data-bs-target="#supply-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-shop me-2"></i>@translator.Translate(userLanguage, "Supplies")</button>
</li>
}
<li class="nav-item ms-auto" role="presentation">
<button class="nav-link @(Model == "settings" ? "active" : "")" id="settings-tab" data-bs-toggle="tab" data-bs-target="#settings-tab-pane" type="button" role="tab"><i class="bi bi-gear me-2"></i>@translator.Translate(userLanguage,"Settings")</button>
</li>
@@ -77,6 +91,8 @@
</div>
</div>
<div class="tab-pane fade" id="supply-tab-pane" role="tabpanel" tabindex="0">
</div>
<div class="tab-pane fade @(Model == "settings" ? "show active" : "")" id="settings-tab-pane" role="tabpanel" tabindex="0">
</div>
</div>
@@ -88,6 +104,12 @@
</div>
</div>
</div>
<div class="modal fade" id="bulkImportModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content" id="bulkImportModalContent">
</div>
</div>
</div>
<script>
loadGarage();
bindWindowResize();

View File

@@ -53,6 +53,10 @@
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableAutoOdometerInsert" checked="@Model.UserConfig.EnableAutoOdometerInsert">
<label class="form-check-label" for="enableAutoOdometerInsert">@translator.Translate(userLanguage, "Auto Insert Odometer Records")<br /><small class="text-body-secondary">@translator.Translate(userLanguage, "Only when Adding Service/Repair/Upgrade/Fuel Record or Completing a Plan")</small></label>
</div>
<div class="form-check form-switch @(User.IsInRole(nameof(UserData.IsRootUser)) ? "" : "d-none")">
<input class="form-check-input" onChange="updateSettings()" type="checkbox" role="switch" id="enableShopSupplies" checked="@Model.UserConfig.EnableShopSupplies">
<label class="form-check-label" for="enableShopSupplies">@translator.Translate(userLanguage, "Shop Supplies")</label>
</div>
@if (User.IsInRole(nameof(UserData.IsRootUser)))
{
<div class="form-check form-switch">
@@ -257,6 +261,7 @@
useMarkDownOnSavedNotes: $("#useMarkDownOnSavedNotes").is(":checked"),
enableAutoReminderRefresh: $("#enableAutoReminderRefresh").is(":checked"),
enableAutoOdometerInsert: $("#enableAutoOdometerInsert").is(":checked"),
enableShopSupplies: $("#enableShopSupplies").is(":checked"),
preferredGasUnit: $("#preferredGasUnit").val(),
preferredGasMileageUnit: $("#preferredFuelMileageUnit").val(),
userLanguage: $("#defaultLanguage").val(),

View File

@@ -24,7 +24,8 @@
<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-5">@translator.Translate(userLanguage, "Description")</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>
</tr>
</thead>
@@ -35,7 +36,8 @@
<td class="col-1"><input class="form-check-input" type="checkbox" onchange="toggleQuantityFieldDisabled(this)" value="@supplyRecord.Id"></td>
<td class="col-2"><input type="text" disabled onchange="recalculateTotal()" class="form-control"></td>
<td class="col-2 supplyquantity">@supplyRecord.Quantity</td>
<td class="col-5">@supplyRecord.Description</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>
</tr>
}

View File

@@ -14,6 +14,7 @@
"HideZero": false,
"EnableAutoReminderRefresh": false,
"EnableAutoOdometerInsert": false,
"EnableShopSupplies": false,
"UseUKMPG": false,
"UseThreeDecimalGasCost": true,
"UseMarkDownOnSavedNotes": false,

View File

@@ -16,6 +16,7 @@ function loadGarage() {
$.get('/Home/Garage', function (data) {
$("#garageContainer").html(data);
loadSettings();
bindTabEvent();
});
}
function loadSettings() {
@@ -23,6 +24,31 @@ function loadSettings() {
$("#settings-tab-pane").html(data);
});
}
function getVehicleSupplyRecords() {
$.get(`/Vehicle/GetSupplyRecordsByVehicleId?vehicleId=0`, function (data) {
if (data) {
$("#supply-tab-pane").html(data);
restoreScrollPosition();
}
});
}
function GetVehicleId() {
return { vehicleId: 0 };
}
function bindTabEvent() {
$('button[data-bs-toggle="tab"]').on('show.bs.tab', function (e) {
switch (e.target.id) {
case "supply-tab":
getVehicleSupplyRecords();
break;
}
switch (e.relatedTarget.id) { //clear out previous tabs with grids in them to help with performance
case "supply-tab":
$("#supply-tab-pane").html("");
break;
}
});
}
function performLogOut() {
$.post('/Login/LogOut', function (data) {
if (data) {

View File

@@ -277,3 +277,113 @@ function updateAggregateLabels() {
var countLabel = $("[data-aggregate-type='count']");
countLabel.text(`${countLabel.text().split(':')[0]}: ${newCount}`)
}
function uploadVehicleFilesAsync(event) {
let formData = new FormData();
var files = event.files;
for (var x = 0; x < files.length; x++) {
formData.append("file", files[x]);
}
sloader.show();
$.ajax({
url: "/Files/HandleMultipleFileUpload",
data: formData,
cache: false,
processData: false,
contentType: false,
type: 'POST',
success: function (response) {
sloader.hide();
if (response.length > 0) {
uploadedFiles.push.apply(uploadedFiles, response);
}
},
error: function () {
sloader.hide();
errorToast("An error has occurred, please check the file size and try again later.")
}
});
}
function deleteFileFromUploadedFiles(fileLocation, event) {
event.parentElement.parentElement.parentElement.remove();
uploadedFiles = uploadedFiles.filter(x => x.location != fileLocation);
}
function editFileName(fileLocation, event) {
Swal.fire({
title: 'Rename File',
html: `
<input type="text" id="newFileName" class="swal2-input" placeholder="New File Name">
`,
confirmButtonText: 'Rename',
focusConfirm: false,
preConfirm: () => {
const newFileName = $("#newFileName").val();
if (!newFileName) {
Swal.showValidationMessage(`Please enter a valid file name`)
}
return { newFileName }
},
}).then(function (result) {
if (result.isConfirmed) {
var linkDisplayObject = $(event.parentElement.parentElement).find('a')[0];
linkDisplayObject.text = result.value.newFileName;
var editFileIndex = uploadedFiles.findIndex(x => x.location == fileLocation);
uploadedFiles[editFileIndex].name = result.value.newFileName;
}
});
}
var scrollPosition = 0;
function saveScrollPosition() {
scrollPosition = $(".vehicleDetailTabContainer").scrollTop();
}
function restoreScrollPosition() {
$(".vehicleDetailTabContainer").scrollTop(scrollPosition);
scrollPosition = 0;
}
function toggleMarkDownOverlay(textAreaName) {
var textArea = $(`#${textAreaName}`);
if ($(".markdown-overlay").length > 0) {
$(".markdown-overlay").remove();
return;
}
var text = textArea.val();
if (text == undefined) {
return;
}
if (text.length > 0) {
var formatted = markdown(text);
//var overlay div
var overlayDiv = `<div class='markdown-overlay' style="z-index: 1060; position:absolute; top:${textArea.css('top')}; left:${textArea.css('left')}; width:${textArea.css('width')}; height:${textArea.css('height')}; padding:${textArea.css('padding')}; overflow-y:auto; background-color:var(--bs-modal-bg);">${formatted}</div>`;
textArea.parent().children(`label[for=${textAreaName}]`).append(overlayDiv);
}
}
function showLinks(e) {
var textAreaName = $(e.parentElement).attr("for");
toggleMarkDownOverlay(textAreaName);
}
function printTab() {
setTimeout(function () {
window.print();
}, 500);
}
function exportVehicleData(mode) {
var vehicleId = GetVehicleId().vehicleId;
$.get('/Vehicle/ExportFromVehicleToCsv', { vehicleId: vehicleId, mode: mode }, function (data) {
if (!data) {
errorToast(genericErrorMessage());
} else {
window.location.href = data;
}
});
}
function showBulkImportModal(mode) {
$.get(`/Vehicle/GetBulkImportModalPartialView?mode=${mode}`, function (data) {
if (data) {
$("#bulkImportModalContent").html(data);
$("#bulkImportModal").modal('show');
}
})
}
function hideBulkImportModal() {
$("#bulkImportModal").modal('hide');
}

View File

@@ -222,27 +222,6 @@ function editVehicle(vehicleId) {
function hideEditVehicleModal() {
$('#editVehicleModal').modal('hide');
}
function exportVehicleData(mode) {
var vehicleId = GetVehicleId().vehicleId;
$.get('/Vehicle/ExportFromVehicleToCsv', { vehicleId: vehicleId, mode: mode }, function (data) {
if (!data) {
errorToast(genericErrorMessage());
} else {
window.location.href = data;
}
});
}
function showBulkImportModal(mode) {
$.get(`/Vehicle/GetBulkImportModalPartialView?mode=${mode}`, function (data) {
if (data) {
$("#bulkImportModalContent").html(data);
$("#bulkImportModal").modal('show');
}
})
}
function hideBulkImportModal(){
$("#bulkImportModal").modal('hide');
}
function deleteVehicle(vehicleId) {
Swal.fire({
title: "Confirm Deletion?",
@@ -260,32 +239,6 @@ function deleteVehicle(vehicleId) {
}
});
}
function uploadVehicleFilesAsync(event) {
let formData = new FormData();
var files = event.files;
for (var x = 0; x < files.length; x++) {
formData.append("file", files[x]);
}
sloader.show();
$.ajax({
url: "/Files/HandleMultipleFileUpload",
data: formData,
cache: false,
processData: false,
contentType: false,
type: 'POST',
success: function (response) {
sloader.hide();
if (response.length > 0) {
uploadedFiles.push.apply(uploadedFiles, response);
}
},
error: function () {
sloader.hide();
errorToast("An error has occurred, please check the file size and try again later.")
}
});
}
function showAddReminderModal(reminderModalInput) {
if (reminderModalInput != undefined) {
$.post('/Vehicle/GetAddReminderRecordPartialView', {reminderModel: reminderModalInput}, function (data) {
@@ -318,47 +271,6 @@ function getVehicleHaveImportantReminders(vehicleId) {
});
}, 500);
}
function printTab() {
setTimeout(function () {
window.print();
}, 500);
}
function deleteFileFromUploadedFiles(fileLocation, event) {
event.parentElement.parentElement.parentElement.remove();
uploadedFiles = uploadedFiles.filter(x => x.location != fileLocation);
}
function editFileName(fileLocation, event) {
Swal.fire({
title: 'Rename File',
html: `
<input type="text" id="newFileName" class="swal2-input" placeholder="New File Name">
`,
confirmButtonText: 'Rename',
focusConfirm: false,
preConfirm: () => {
const newFileName = $("#newFileName").val();
if (!newFileName) {
Swal.showValidationMessage(`Please enter a valid file name`)
}
return { newFileName }
},
}).then(function (result) {
if (result.isConfirmed) {
var linkDisplayObject = $(event.parentElement.parentElement).find('a')[0];
linkDisplayObject.text = result.value.newFileName;
var editFileIndex = uploadedFiles.findIndex(x => x.location == fileLocation);
uploadedFiles[editFileIndex].name = result.value.newFileName;
}
});
}
var scrollPosition = 0;
function saveScrollPosition() {
scrollPosition = $(".vehicleDetailTabContainer").scrollTop();
}
function restoreScrollPosition() {
$(".vehicleDetailTabContainer").scrollTop(scrollPosition);
scrollPosition = 0;
}
function moveRecord(recordId, source, dest) {
$("#workAroundInput").show();
var friendlySource = "";
@@ -416,24 +328,3 @@ function moveRecord(recordId, source, dest) {
}
});
}
function toggleMarkDownOverlay(textAreaName) {
var textArea = $(`#${textAreaName}`);
if ($(".markdown-overlay").length > 0) {
$(".markdown-overlay").remove();
return;
}
var text = textArea.val();
if (text == undefined) {
return;
}
if (text.length > 0) {
var formatted = markdown(text);
//var overlay div
var overlayDiv = `<div class='markdown-overlay' style="z-index: 1060; position:absolute; top:${textArea.css('top')}; left:${textArea.css('left')}; width:${textArea.css('width')}; height:${textArea.css('height')}; padding:${textArea.css('padding')}; overflow-y:auto; background-color:var(--bs-modal-bg);">${formatted}</div>`;
textArea.parent().children(`label[for=${textAreaName}]`).append(overlayDiv);
}
}
function showLinks(e) {
var textAreaName = $(e.parentElement).attr("for");
toggleMarkDownOverlay(textAreaName);
}