Enhance subuser profile management with editable name and email fields

- Added support for subusers to update their name and email via modals in `UserProfile.vue`.
- Updated `SubuserGrantSelector` styles with improved layout and accessibility for compact mode.
- Refined `SessionUser` logic to conditionally render visible profile attributes and actions based on subuser status.
- Adjusted navigation permissions to remove redundant `user_invoices` permission check.
This commit is contained in:
Jeppe Bundgaard
2026-02-12 18:07:02 +01:00
parent caff98690c
commit ba63aab53b
5 changed files with 262 additions and 6 deletions
@@ -1,5 +1,5 @@
<script setup>
import {defineProps, onMounted, ref} from 'vue';
import {defineProps, onMounted, ref, watch} from 'vue';
import {parseError, getError} from "@/components/request/HandleGlobalError.vue";
const props = defineProps({
@@ -27,6 +27,11 @@ const isEditting = ref(false);
const v_model = ref(props.value);
// Watch for changes in the value prop and update v_model
watch(() => props.value, (newValue) => {
v_model.value = newValue;
});
const errorId = ref(null);
const parseErrorOnSave = (error) => {
+1 -1
View File
@@ -52,7 +52,7 @@ const menu_items = ref([
icon: 'fas fa-file-invoice',
children: [],
permissions: [
'user_invoices'
'user'
]
},
]);
@@ -72,7 +72,7 @@ const items = computed<NavigationItemProps[]>(() => [
label: SessionUser.functions.ucFirst(SessionUser.objects.collectedOrderInvoices.meta.labels.multiple),
to: `/user/invoices`,
type: 'link',
permissions: ['user', 'user_invoices'],
permissions: ['user'],
},
{
label: 'Chauffører',
@@ -59,7 +59,10 @@ const selectedGrantName = computed(() => {
:class="{ 'is-active': grant.billing_customer_number === selectedCustomerNumber }"
@click="selectGrant(grant.billing_customer_number)"
>
{{ grant.name || 'Kunde #' + grant.billing_customer_number }}
<span class="dropdown-item-check">
<i class="fas fa-check" v-if="grant.billing_customer_number === selectedCustomerNumber"></i>
</span>
<span class="dropdown-item-text">{{ grant.name || 'Kunde #' + grant.billing_customer_number }}</span>
</a>
</div>
</div>
@@ -110,16 +113,62 @@ const selectedGrantName = computed(() => {
}
.grant-selector.is-compact .dropdown-menu {
min-width: 200px;
min-width: 250px;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
.grant-selector.is-compact .dropdown-content {
border-radius: 8px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.08);
border: 1px solid rgba(0, 0, 0, 0.05);
padding: 0.5rem 0;
}
.grant-selector.is-compact .dropdown-item {
cursor: pointer;
padding: 0.625rem 1rem;
font-size: 0.9rem;
transition: background-color 0.15s ease, color 0.15s ease;
border-radius: 0;
display: flex;
align-items: center;
gap: 0.5rem;
}
.grant-selector.is-compact .dropdown-item:hover {
background-color: #f5f5f5;
}
.grant-selector.is-compact .dropdown-item.is-active {
background-color: #3273dc;
color: white;
font-weight: 500;
}
.grant-selector.is-compact .dropdown-item.is-active:hover {
background-color: #2366d1;
}
.grant-selector.is-compact .dropdown-item-check {
width: 1.25rem;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
color: #3273dc;
}
.grant-selector.is-compact .dropdown-item.is-active .dropdown-item-check {
color: white;
}
.grant-selector.is-compact .dropdown-item-text {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.label {
@@ -359,6 +359,125 @@ const onClickSavePhoneNumber = async () => {
})
}
/** Subuser name change */
const onClickSaveSubuserName = async () => {
await Swal.fire({
title: 'Indtast dit navn',
input: 'text',
inputLabel: 'Navn',
inputPlaceholder: 'Dit navn',
inputValue: SessionUser.subuser.name.value || '',
showCancelButton: true,
preConfirm: (value) => {
if (!value) {
Swal.showValidationMessage('Indtast venligst dit navn');
return;
}
if (value.length < 2) {
Swal.showValidationMessage('Navnet skal være mindst 2 tegn langt');
return;
}
isLoading.value = true;
Swal.showLoading();
return value;
},
allowOutsideClick: () => !Swal.isLoading(),
showLoaderOnConfirm: true,
}).then((result) => {
if (result.isConfirmed) {
const name = result.value;
SessionUser.request(
'/subusers/me',
'PUT',
{ name: name }
).then((response) => {
if (response.status === 200) {
Swal.fire({
title: 'Navn gemt',
text: 'Dit navn er blevet opdateret.',
icon: 'success',
});
SessionUser.subuser.name.value = name;
} else {
Swal.fire({
title: 'Fejl',
text: 'Der opstod en fejl under gemme af navnet.',
icon: 'error',
});
}
}).catch((error) => {
Swal.fire({
title: 'Fejl',
text: SessionUser.functions.parseErrorMessage(error),
icon: 'error',
});
}).finally(() => {
isLoading.value = false;
});
}
});
}
/** Subuser email change */
const onClickSaveSubuserEmail = async () => {
await Swal.fire({
title: 'Indtast din e-mailadresse',
input: 'text',
inputLabel: 'E-mail',
inputPlaceholder: 'Din e-mailadresse',
inputValue: SessionUser.subuser.email.value || '',
showCancelButton: true,
preConfirm: (value) => {
if (!value) {
Swal.showValidationMessage('Indtast venligst din e-mailadresse');
return;
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
Swal.showValidationMessage('Indtast venligst en gyldig e-mailadresse');
return;
}
isLoading.value = true;
Swal.showLoading();
return value;
},
allowOutsideClick: () => !Swal.isLoading(),
showLoaderOnConfirm: true,
}).then((result) => {
if (result.isConfirmed) {
const email = result.value;
SessionUser.request(
'/subusers/me',
'PUT',
{ email: email }
).then((response) => {
if (response.status === 200) {
Swal.fire({
title: 'E-mail gemt',
text: 'Din e-mailadresse er blevet opdateret.',
icon: 'success',
});
SessionUser.subuser.email.value = email;
} else {
Swal.fire({
title: 'Fejl',
text: 'Der opstod en fejl under gemme af e-mailadressen.',
icon: 'error',
});
}
}).catch((error) => {
Swal.fire({
title: 'Fejl',
text: SessionUser.functions.parseErrorMessage(error),
icon: 'error',
});
}).finally(() => {
isLoading.value = false;
});
}
});
}
const onClickSaveWashCertificateEmail = async () => {
// Show the wash certificate email input dialog
await Swal.fire({
@@ -416,6 +535,7 @@ const onClickSaveWashCertificateEmail = async () => {
<UserDashboardPageWrapper title="Brugerprofil">
<!-- Invoicing -->
<ConfigurationCategory
v-if="!SessionUser.isSubuser.value"
class="mt-2"
:module="SessionUser.functions.device.isMobile() ? `` : `Brugerprofil`"
title="Fakturaoplysninger"
@@ -439,6 +559,7 @@ const onClickSaveWashCertificateEmail = async () => {
</ConfigurationCategory>
<!-- Security -->
<ConfigurationCategory
v-if="!SessionUser.isSubuser.value"
class="mt-2"
:module="SessionUser.functions.device.isMobile() ? `` : `Brugerprofil`"
title="Sikkerhed"
@@ -505,13 +626,92 @@ const onClickSaveWashCertificateEmail = async () => {
</button>
</div>
</ConfigurationCategory>
<!-- Subuser - Profile Information -->
<ConfigurationCategory
v-if="SessionUser.isSubuser.value"
class="mt-2"
:module="SessionUser.functions.device.isMobile() ? `` : `Brugerprofil`"
title="Underbruger"
subtitle="Profiloplysninger"
description="Dine underbruger-oplysninger"
icon="fas fa-user"
>
<!-- Username (read-only) -->
<ConfigurationInput
title="Brugernavn"
description="Dit brugernavn kan ikke ændres."
:value="SessionUser.subuser.username.value"
:disabled="true"
/>
<!-- Name (editable) -->
<ConfigurationInput
title="Navn"
description="Dit visningsnavn som underbruger."
:value="SessionUser.subuser.name.value"
:disabled="true"
/>
<button
class="button is-link is-small mt-2"
@click="onClickSaveSubuserName"
:disabled="isLoading"
>
<span>
<i class="fas fa-edit"></i>
<span class="ml-2">Rediger navn</span>
</span>
</button>
</ConfigurationCategory>
<!-- Subuser - Contact Information -->
<ConfigurationCategory
v-if="SessionUser.isSubuser.value"
class="mt-2"
:module="SessionUser.functions.device.isMobile() ? `` : `Brugerprofil`"
title="Underbruger"
subtitle="Kontaktoplysninger"
description="Dine kontaktoplysninger som underbruger"
icon="fas fa-address-card"
>
<!-- Email (editable) -->
<ConfigurationInput
title="E-mail"
description="Din e-mailadresse som underbruger."
:value="SessionUser.subuser.email.value"
:disabled="true"
/>
<button
class="button is-link is-small mt-2"
@click="onClickSaveSubuserEmail"
:disabled="isLoading"
>
<span>
<i class="fas fa-edit"></i>
<span class="ml-2">Rediger e-mail</span>
</span>
</button>
<hr class="my-4"/>
<!-- Phone number (read-only) -->
<ConfigurationInput
title="Telefonnummer"
description="Dit telefonnummer som underbruger."
:value="SessionUser.subuser.phone.number.value"
:disabled="true"
/>
<!-- Country code (read-only) -->
<ConfigurationInput
title="Landekode"
description="Din landekode for telefonnummeret."
:value="SessionUser.subuser.phone.country_code.value"
:disabled="true"
/>
</ConfigurationCategory>
<!-- Subuser - Grant selection -->
<ConfigurationCategory
v-if="SessionUser.isSubuser.value"
class="mt-2"
:module="SessionUser.functions.device.isMobile() ? `` : `Brugerprofil`"
title="Underbruger"
description="Du er logget ind som underbruger. Vælg hvilken kunde du vil handle på vegne af."
subtitle="Kundevalg"
description="Vælg hvilken kunde du vil handle på vegne af."
icon="fas fa-users"
>
<SubuserGrantSelector :show-label="false" />
@@ -521,6 +721,7 @@ const onClickSaveWashCertificateEmail = async () => {
</ConfigurationCategory>
<!-- Notifications - email notifications -->
<ConfigurationCategory
v-if="!SessionUser.isSubuser.value"
class="mt-2"
:module="SessionUser.functions.device.isMobile() ? `` : `Brugerprofil`"
title="Notifikationer"
@@ -557,6 +758,7 @@ const onClickSaveWashCertificateEmail = async () => {
</ConfigurationCategory>
<!-- Notifications - SMS notifications -->
<ConfigurationCategory
v-if="!SessionUser.isSubuser.value"
class="mt-2"
:module="SessionUser.functions.device.isMobile() ? `` : `Brugerprofil`"
title="Notifikationer"