front end for managing households

This commit is contained in:
DESKTOP-T0O5CDB\DESK-555BD 2025-11-11 16:04:13 -07:00
parent b98585006b
commit a137fbaa52
14 changed files with 216 additions and 15 deletions

View File

@ -289,6 +289,24 @@ namespace CarCareTracker.Controllers
var userName = User.Identity.Name;
return PartialView("_AccountModal", new UserData() { EmailAddress = emailAddress, UserName = userName });
}
[HttpGet]
public IActionResult GetHouseholdModal()
{
var households = _userLogic.GetHouseholdForParentUserId(GetUserID());
return PartialView("_UserHouseholdModal", households);
}
[HttpPost]
public IActionResult RemoveUserFromHousehold(int userId)
{
var result = _userLogic.DeleteUserFromHousehold(GetUserID(), userId);
return Json(result);
}
[HttpPost]
public IActionResult AddUserToHousehold(string username)
{
var result = _userLogic.AddUserToHousehold(GetUserID(), username);
return Json(result);
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpGet]
public IActionResult GetRootAccountInformationModal()

View File

@ -118,7 +118,8 @@ namespace CarCareTracker.Controllers
}
//get collaborators
var collaborators = _userLogic.GetCollaboratorsForVehicle(vehicleId);
viewModel.Collaborators = collaborators;
var userCanModify = _userLogic.UserCanDirectlyEditVehicle(GetUserID(), vehicleId);
viewModel.Collaborators = new VehicleCollaboratorViewModel { CanModifyCollaborators = userCanModify, Collaborators = collaborators};
//get MPG per month.
var mileageData = _gasHelper.GetGasRecordViewModels(gasRecords, userConfig.UseMPG, !vehicleData.IsElectric && userConfig.UseUKMPG);
string preferredFuelMileageUnit = _config.GetUserConfig(User).PreferredGasMileageUnit;
@ -176,6 +177,12 @@ namespace CarCareTracker.Controllers
public IActionResult GetCollaboratorsForVehicle(int vehicleId)
{
var result = _userLogic.GetCollaboratorsForVehicle(vehicleId);
var userCanModify = _userLogic.UserCanDirectlyEditVehicle(GetUserID(), vehicleId);
var viewModel = new VehicleCollaboratorViewModel
{
Collaborators = result,
CanModifyCollaborators = userCanModify
};
return PartialView("_Collaborators", result);
}
[TypeFilter(typeof(CollaboratorFilter))]

View File

@ -217,10 +217,13 @@ namespace CarCareTracker.Controllers
if (vehicleIds.Count() == 1)
{
//only one vehicle to manage
if (_userLogic.UserCanEditVehicle(GetUserID(), vehicleIds.First()))
if (_userLogic.UserCanDirectlyEditVehicle(GetUserID(), vehicleIds.First()))
{
viewModel.CommonCollaborators = _userLogic.GetCollaboratorsForVehicle(vehicleIds.First()).Select(x=>x.UserName).ToList();
viewModel.VehicleIds.Add(vehicleIds.First());
} else
{
viewModel.CanModifyCollaborators = false;
}
}
else
@ -228,11 +231,14 @@ namespace CarCareTracker.Controllers
List<UserCollaborator> allCollaborators = new List<UserCollaborator>();
foreach (int vehicleId in vehicleIds)
{
if (_userLogic.UserCanEditVehicle(GetUserID(), vehicleId))
if (_userLogic.UserCanDirectlyEditVehicle(GetUserID(), vehicleId))
{
var vehicleCollaborators = _userLogic.GetCollaboratorsForVehicle(vehicleId);
allCollaborators.AddRange(vehicleCollaborators);
viewModel.VehicleIds.Add(vehicleId);
} else
{
viewModel.CanModifyCollaborators = false;
}
}
var groupedCollaborations = allCollaborators.GroupBy(x => x.UserName);

View File

@ -12,8 +12,10 @@ namespace CarCareTracker.Logic
OperationResponse AddCollaboratorToVehicle(int vehicleId, string username);
List<Vehicle> FilterUserVehicles(List<Vehicle> results, int userId);
bool UserCanEditVehicle(int userId, int vehicleId);
bool UserCanDirectlyEditVehicle(int userId, int vehicleId);
bool DeleteAllAccessToVehicle(int vehicleId);
bool DeleteAllAccessToUser(int userId);
List<UserHouseholdViewModel> GetHouseholdForParentUserId(int parentUserId);
OperationResponse AddUserToHousehold(int parentUserId, string childUsername);
bool DeleteUserFromHousehold(int parentUserId, int childUserId);
bool DeleteAllHouseholdByParentUserId(int parentUserId);
@ -162,6 +164,19 @@ namespace CarCareTracker.Logic
}
return false;
}
public bool UserCanDirectlyEditVehicle(int userId, int vehicleId)
{
if (userId == -1)
{
return true;
}
var userAccess = _userAccess.GetUserAccessByVehicleAndUserId(userId, vehicleId);
if (userAccess != null && userAccess.Id.UserId == userId && userAccess.Id.VehicleId == vehicleId)
{
return true;
}
return false;
}
public bool DeleteAllAccessToVehicle(int vehicleId)
{
var result = _userAccess.DeleteAllAccessRecordsByVehicleId(vehicleId);
@ -172,6 +187,22 @@ namespace CarCareTracker.Logic
var result = _userAccess.DeleteAllAccessRecordsByUserId(userId);
return result;
}
public List<UserHouseholdViewModel> GetHouseholdForParentUserId(int parentUserId)
{
var result = _userHouseholdData.GetUserHouseholdByParentUserId(parentUserId);
var convertedResult = new List<UserHouseholdViewModel>();
//convert useraccess to usercollaborator
foreach (UserHousehold userHouseholdAccess in result)
{
var userCollaborator = new UserHouseholdViewModel
{
UserName = _userData.GetUserRecordById(userHouseholdAccess.Id.ChildUserId).UserName,
UserHousehold = userHouseholdAccess.Id
};
convertedResult.Add(userCollaborator);
}
return convertedResult;
}
public OperationResponse AddUserToHousehold(int parentUserId, string childUsername)
{
//attempting to add to root user
@ -184,6 +215,11 @@ namespace CarCareTracker.Logic
if (existingUser.Id != default)
{
//user exists.
//check if user is trying to add themselves
if (parentUserId == existingUser.Id)
{
return OperationResponse.Failed("Cannot add yourself to your household");
}
//check if user already belongs to the household
var householdAccess = _userHouseholdData.GetUserHouseholdByParentAndChildUserId(parentUserId, existingUser.Id);
if (householdAccess != null && householdAccess.Id.ChildUserId == existingUser.Id && householdAccess.Id.ParentUserId == parentUserId)
@ -192,7 +228,7 @@ namespace CarCareTracker.Logic
}
//check if a circular dependency will exist
var circularHouseholdAccess = _userHouseholdData.GetUserHouseholdByParentAndChildUserId(existingUser.Id, parentUserId);
if (circularHouseholdAccess != null && circularHouseholdAccess.Id.ChildUserId == existingUser.Id && circularHouseholdAccess.Id.ParentUserId == parentUserId)
if (circularHouseholdAccess != null && circularHouseholdAccess.Id.ChildUserId == parentUserId && circularHouseholdAccess.Id.ParentUserId == existingUser.Id)
{
return OperationResponse.Failed("Circular dependency is not allowed");
}

View File

@ -8,7 +8,7 @@
public CostMakeUpForVehicle CostMakeUpForVehicle { get; set; } = new CostMakeUpForVehicle();
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 VehicleCollaboratorViewModel Collaborators { get; set; } = new VehicleCollaboratorViewModel();
public bool CustomWidgetsConfigured { get; set; } = false;
public List<ImportMode> AvailableMetrics { get; set; } = new List<ImportMode>();
public bool HasVehicleImageMap { get; set; } = false;

View File

@ -5,5 +5,6 @@
public List<int> VehicleIds { get; set; } = new List<int>();
public List<string> CommonCollaborators { get; set; } = new List<string>();
public List<string> PartialCollaborators { get; set; } = new List<string>();
public bool CanModifyCollaborators { get; set; } = true;
}
}
}

View File

@ -0,0 +1,8 @@
namespace CarCareTracker.Models
{
public class UserHouseholdViewModel
{
public string UserName { get; set; }
public HouseholdAccess UserHousehold { get; set; }
}
}

View File

@ -0,0 +1,8 @@
namespace CarCareTracker.Models
{
public class VehicleCollaboratorViewModel
{
public List<UserCollaborator> Collaborators { get; set; }
public bool CanModifyCollaborators { get; set; } = true;
}
}

View File

@ -68,7 +68,11 @@
<li>
<button class="dropdown-item" onclick="showAccountInformationModal()"><i class="bi bi-person-gear me-2"></i>@translator.Translate(userLanguage, "Profile")</button>
</li>
<li>
<button class="dropdown-item" onclick="showHouseholdModal()"><i class="bi bi-house me-2"></i>@translator.Translate(userLanguage, "Household")</button>
</li>
}
<li><hr class="dropdown-divider"></li>
<li>
<button class="dropdown-item" onclick="performLogOut()"><i class="bi bi-box-arrow-right me-2"></i>@translator.Translate(userLanguage, "Logout")</button>
</li>
@ -121,6 +125,9 @@
<li>
<button class="nav-link" onclick="showAccountInformationModal()"><span class="display-3 ms-2"><i class="bi bi-person-gear me-2"></i>@translator.Translate(userLanguage, "Profile")</span></button>
</li>
<li>
<button class="nav-link" onclick="showHouseholdModal()"><span class="display-3 ms-2"><i class="bi bi-house me-2"></i>@translator.Translate(userLanguage, "Household")</span></button>
</li>
}
<li class="nav-item" role="presentation">
<button class="nav-link" onclick="performLogOut()"><span class="display-3 ms-2"><i class="bi bi-box-arrow-right me-2"></i>@translator.Translate(userLanguage,"Logout")</span></button>
@ -159,6 +166,12 @@
</div>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="householdModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content" id="householdModalContent">
</div>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="attachmentPreviewModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-fullscreen" role="document">
<div class="modal-content frosted" id="attachmentPreviewModalContent">

View File

@ -0,0 +1,40 @@
@using CarCareTracker.Helper
@inject IConfigHelper config
@inject ITranslationHelper translator
@model List<UserHouseholdViewModel>
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
<div class="modal-header">
<div class="d-flex align-items-center">
<span class="lead">@translator.Translate(userLanguage, "Manage Household")</span>
</div>
<div class="d-flex align-items-center ms-auto">
<button onclick="addUserToHousehold()" class="btn btn-primary">
<i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Add User")
</button>
<button type="button" class="btn-close ms-2" onclick="hideHouseholdModal()" aria-label="Close"></button>
</div>
</div>
<div class="modal-body">
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-10">@translator.Translate(userLanguage, "Username")</th>
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Delete")</th>
</tr>
</thead>
<tbody id="tokenTable">
@foreach(UserHouseholdViewModel viewModel in Model)
{
<tr class="d-flex">
<td class="col-10">@viewModel.UserName</td>
<td class="col-2">
<button type="button" class="btn btn-danger" onclick="removeUserFromHousehold(@viewModel.UserHousehold.ChildUserId, this)"><i class="bi bi-trash"></i></button>
</td>
</tr>
}
</tbody>
</table>
</div>

View File

@ -5,14 +5,17 @@
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
@model List<UserCollaborator>
@model VehicleCollaboratorViewModel
<div class="row">
<div class="col-8">
<div class="@(Model.CanModifyCollaborators ? "col-8" : "col-12")">
<span class="lead">@translator.Translate(userLanguage, "Collaborators")</span>
</div>
<div class="col-4">
<button onclick="addCollaborator()" class="btn btn-link btn-sm"><i class="bi bi-person-add"></i></button>
</div>
@if (Model.CanModifyCollaborators)
{
<div class="col-4">
<button onclick="addCollaborator()" class="btn btn-link btn-sm"><i class="bi bi-person-add"></i></button>
</div>
}
</div>
<div class="row">
<table class="table table-hover">
@ -23,12 +26,12 @@
</tr>
</thead>
<tbody>
@foreach (UserCollaborator user in Model)
@foreach (UserCollaborator user in Model.Collaborators)
{
<tr class="d-flex">
<td class="col-8">@user.UserName</td>
<td class="col-4">
@if(User.Identity.Name != user.UserName)
@if(User.Identity.Name != user.UserName && Model.CanModifyCollaborators)
{
<button onclick="deleteCollaborator(@user.UserVehicle.UserId, @user.UserVehicle.VehicleId)" class="btn btn-outline-danger btn-sm"><i class="bi bi-trash"></i></button>
}

View File

@ -11,6 +11,8 @@
<h5 class="modal-title" id="userCollaboratorsModalLabel">@translator.Translate(userLanguage, "Manage Collaborators")</h5>
<button type="button" class="btn-close" onclick="hideCollaboratorsModal()" aria-label="Close"></button>
</div>
@if (Model.CanModifyCollaborators)
{
<div class="modal-body">
@if (showTwoColumns)
{
@ -105,4 +107,17 @@
@:vehiclesToEdit.push(@recordId);
}
adjustCollaboratorsModalSize(@showTwoColumns.ToString().ToLower());
</script>
</script>
}
else
{
<div class="modal-body">
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-center">
<span class="lead text-center">@translator.Translate(userLanguage, "You don't have access to manage collaborators for these vehicles")</span>
</div>
</div>
</div>
</div>
}

File diff suppressed because one or more lines are too long

View File

@ -576,6 +576,52 @@ function sortVehicles(desc) {
sortedRow.push($('.garage-item-add'))
$('.vehiclesContainer').html(sortedRow);
}
function showHouseholdModal() {
$.get('/Home/GetHouseholdModal', function (data) {
$("#householdModalContent").html(data);
$("#householdModal").modal('show');
})
}
function hideHouseholdModal() {
$("#householdModal").modal('hide');
}
function removeUserFromHousehold(userId) {
$.post('/Home/RemoveUserFromHousehold', { userId: userId }, function (data) {
if (data) {
showHouseholdModal();
} else {
errorToast(genericErrorMessage())
}
})
}
function addUserToHousehold() {
Swal.fire({
title: 'Add User',
html: `
<input type="text" id="inputUserName" class="swal2-input" placeholder="Username" onkeydown="handleSwalEnter(event)">
`,
confirmButtonText: 'Add',
focusConfirm: false,
preConfirm: () => {
const userName = $("#inputUserName").val();
if (!userName) {
Swal.showValidationMessage(`Please enter a username`);
}
return { userName }
},
}).then(function (result) {
if (result.isConfirmed) {
$.post('/Home/AddUserToHousehold', { username: result.value.userName }, function (data) {
if (data.success) {
showHouseholdModal();
} else {
errorToast(data.message);
}
});
}
});
}
function showAccountInformationModal() {
$.get('/Home/GetUserAccountInformationModal', function (data) {
$('#accountInformationModalContent').html(data);