- Replaced hardcoded dashboard text in `UserDashboard.vue` with dynamic translations using `$t`. - Added new translation keys in `en.json` and `da.json` for user dashboard actions, descriptions, and messages. - Improved multilingual support for subuser actions, empty state messages, and contact information.
310 lines
12 KiB
Vue
310 lines
12 KiB
Vue
<script setup lang="ts">
|
|
import SessionUser from "@/components/session/token/SessionUser.vue";
|
|
import WhiteBoxCard from "@/components/displays/boxes/WhiteBoxCard.vue";
|
|
import SubuserGrantSelector from "@/components/session/subuser/SubuserGrantSelector.vue";
|
|
import {ref, onMounted, nextTick, computed, onUnmounted} from "vue";
|
|
import { IS_DEV } from '@/config.js';
|
|
|
|
/**
|
|
* Subuser grant selection gate
|
|
* Subusers must select a grant before accessing the dashboard
|
|
*/
|
|
const isSubuser = computed(() => SessionUser.isSubuser.value);
|
|
const hasSelectedGrant = computed(() => {
|
|
if (!isSubuser.value) return true;
|
|
return SessionUser.subuser.selectedGrantCustomerNumber.value !== null;
|
|
});
|
|
/**
|
|
* Make sure the height of the cards are equal
|
|
*/
|
|
const cardMinHeight = ref(0); // Pixel height
|
|
const setEqualHeights = () => {
|
|
nextTick(() => {
|
|
if (SessionUser.functions.device.isMobile()) {
|
|
return;
|
|
}
|
|
const cardContents = document.querySelectorAll('.identical-height-spacer');
|
|
let maxHeight = 0;
|
|
cardContents.forEach((element) => {
|
|
const elementHeight = element.clientHeight;
|
|
if (elementHeight > maxHeight) {
|
|
maxHeight = elementHeight;
|
|
}
|
|
});
|
|
cardMinHeight.value = maxHeight;
|
|
});
|
|
};
|
|
const handleResize = () => {
|
|
setEqualHeights();
|
|
};
|
|
onMounted(() => {
|
|
setEqualHeights();
|
|
window.addEventListener('resize', handleResize);
|
|
});
|
|
const cleanup = () => {
|
|
window.removeEventListener('resize', handleResize);
|
|
};
|
|
onUnmounted(() => {
|
|
cleanup();
|
|
});
|
|
|
|
const isTablet = computed(() => {
|
|
return SessionUser.functions.device.isTablet();
|
|
});
|
|
|
|
/**
|
|
* Check if the subuser has permission to any of the dashboard shortcuts
|
|
*/
|
|
const hasAnyShortcutPermission = computed(() => {
|
|
// Regular users always have access
|
|
if (!isSubuser.value) return true;
|
|
|
|
// Check if subuser has any of the shortcut permissions
|
|
return (
|
|
SessionUser.hasPermission('user') ||
|
|
SessionUser.hasPermission('BOOKINGS_ADD') ||
|
|
SessionUser.hasPermission('VEHICLES_ADD') ||
|
|
SessionUser.hasPermission('ORDERS_LIST') ||
|
|
SessionUser.hasPermission('user_invoices')
|
|
);
|
|
});
|
|
|
|
const windowInnerWidth = ref(window.innerWidth);
|
|
const updateWindowInnerWidth = () => {
|
|
windowInnerWidth.value = window.innerWidth;
|
|
// Trigger any layout updates if necessary
|
|
classes.value; // Access to trigger recomputation
|
|
};
|
|
window.addEventListener('resize', updateWindowInnerWidth);
|
|
onUnmounted(() => {
|
|
window.removeEventListener('resize', updateWindowInnerWidth);
|
|
});
|
|
|
|
type ClassesType = {
|
|
column?: string[];
|
|
title?: string[];
|
|
button?: string[];
|
|
};
|
|
|
|
const classes = computed(() => {
|
|
if (computed(() => SessionUser.functions.device.isMobile()).value) {
|
|
return {
|
|
column: ['is-12-mobile', 'is-half-tablet', 'is-half-desktop'],
|
|
title: [],
|
|
button: [],
|
|
} as ClassesType;
|
|
} else if (SessionUser.functions.device.isTablet()) {
|
|
return {
|
|
column: ['is-half-tablet', 'is-12-mobile', 'is-half-desktop'],
|
|
title: ['is-size-7', 'has-text-weight-bold'],
|
|
button: ['is-size-7-tablet'],
|
|
} as ClassesType;
|
|
} else if ((SessionUser.functions.device.isDesktop() || SessionUser.functions.device.isWidescreen()) && !SessionUser.functions.device.isUltraWideScreen()) {
|
|
return {
|
|
column: ['is-half-desktop', 'is-half-tablet', 'is-12-mobile'],
|
|
title: [],
|
|
button: [],
|
|
} as ClassesType;
|
|
} else if (SessionUser.functions.device.isUltraWideScreen()) {
|
|
return {
|
|
column: ['is-one-third-desktop', 'is-half-tablet', 'is-12-mobile'],
|
|
title: [],
|
|
button: [],
|
|
} as ClassesType;
|
|
} else {
|
|
return {
|
|
// Default to desktop layout
|
|
column: ['is-half'],
|
|
title: [],
|
|
button: [],
|
|
} as ClassesType;
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<!-- Subuser grant selection gate -->
|
|
<div v-if="isSubuser && !hasSelectedGrant" class="box">
|
|
<div class="has-text-centered mb-4">
|
|
<span class="icon is-large has-text-info">
|
|
<i class="fas fa-users fa-2x"></i>
|
|
</span>
|
|
<h2 class="title is-4 mt-3">{{ $t('user_home.select_customer') }}</h2>
|
|
<p class="subtitle is-6">{{ $t('user_home.select_customer_desc') }}</p>
|
|
</div>
|
|
<SubuserGrantSelector :showLabel="false" />
|
|
</div>
|
|
|
|
<!-- Empty state for subusers without permissions -->
|
|
<div v-else-if="isSubuser && !hasAnyShortcutPermission" class="box has-text-centered">
|
|
<span class="icon is-large has-text-grey-light">
|
|
<i class="fas fa-inbox fa-3x"></i>
|
|
</span>
|
|
<h2 class="title is-4 mt-4 has-text-grey">{{ $t('user_home.empty_state_title') }}</h2>
|
|
<p class="subtitle is-6 has-text-grey">{{ $t('user_home.empty_state_desc') }}</p>
|
|
</div>
|
|
|
|
<!-- Main dashboard content -->
|
|
<div v-else class="columns is-vcentered mb-4 is-multiline">
|
|
<div class="column is-12">
|
|
<span class="is-auto-size-text is-size-6 has-text-weight-bold">
|
|
{{ $t('user_home.welcome') }} {{ SessionUser.getName() }}
|
|
</span>
|
|
</div>
|
|
<div class="column is-half-desktop is-hidden-desktop is-12-tablet" v-show="IS_DEV">
|
|
<!-- Start wash card -->
|
|
<WhiteBoxCard :force-state="true" :defaultOpen="true" :toggleable="false" :loading="false">
|
|
<template #header>
|
|
<div class="card-header-title has-text-link has-text-weight-bold">
|
|
{{ $t('user_home.start_wash') }}
|
|
</div>
|
|
<div class="card-header-icon has-text-link">
|
|
<i class="fas fa-soap"></i>
|
|
</div>
|
|
</template>
|
|
<template #content>
|
|
<div class="identical-height-spacer" :style="{ minHeight: cardMinHeight + 'px' }">
|
|
{{ $t('user_home.start_wash_desc') }}
|
|
</div>
|
|
</template>
|
|
<template #footer>
|
|
<div class="card-footer-item">
|
|
<router-link to="/user/wash/start" class="button is-link is-fullwidth">
|
|
{{ $t('user_home.start_wash') }}
|
|
</router-link>
|
|
</div>
|
|
</template>
|
|
</WhiteBoxCard>
|
|
</div>
|
|
<div class="column" :class="classes.column" v-if="SessionUser.hasPermission('user') || SessionUser.hasPermission('BOOKINGS_ADD')">
|
|
<!-- Book wash card -->
|
|
<WhiteBoxCard :force-state="true" :defaultOpen="true" :toggleable="false" :loading="false">
|
|
<template #header>
|
|
<div class="card-header-title" :class="classes.title">
|
|
{{ $t('user_home.book_wash') }}
|
|
</div>
|
|
<div class="card-header-icon">
|
|
<i class="fas fa-calendar-check"></i>
|
|
</div>
|
|
</template>
|
|
<template #content>
|
|
<div class="identical-height-spacer" :style="{ minHeight: cardMinHeight + 'px' }">
|
|
{{ $t('user_home.book_wash_desc') }}
|
|
</div>
|
|
<!--<b-loading :is-full-page="false" :model-value="true" :can-cancel="true"/>-->
|
|
</template>
|
|
<template #footer>
|
|
<div class="card-footer-item">
|
|
<router-link to="/user/bookings/book" class="button is-link is-fullwidth" :class="classes.button">
|
|
{{ $t('user_home.book_wash') }}
|
|
</router-link>
|
|
</div>
|
|
</template>
|
|
</WhiteBoxCard>
|
|
</div>
|
|
<div class="column" :class="classes.column" v-if="SessionUser.hasPermission('user') || SessionUser.hasPermission('VEHICLES_ADD')">
|
|
<!-- Add vehicle card -->
|
|
<WhiteBoxCard :force-state="true" :defaultOpen="true" :toggleable="false" :loading="false">
|
|
<template #header>
|
|
<div class="card-header-title" :class="classes.title">
|
|
{{ $t('user_home.create_vehicle') }}
|
|
</div>
|
|
<div class="card-header-icon">
|
|
<i class="fas fa-truck"></i>
|
|
</div>
|
|
</template>
|
|
<template #content>
|
|
<div class="identical-height-spacer" :style="{ minHeight: cardMinHeight + 'px' }">
|
|
{{ $t('user_home.create_vehicle_desc') }}
|
|
</div>
|
|
<!--<b-loading :is-full-page="false" :model-value="true" :can-cancel="true"/>-->
|
|
</template>
|
|
<template #footer>
|
|
<div class="card-footer-item">
|
|
<router-link to="/user/vehicles" class="button is-link is-fullwidth" :class="classes.button">
|
|
{{ $t('user_home.create_vehicle') }}
|
|
</router-link>
|
|
</div>
|
|
</template>
|
|
</WhiteBoxCard>
|
|
</div>
|
|
<div class="column" :class="classes.column" v-if="SessionUser.hasPermission('user') || SessionUser.hasPermission('ORDERS_LIST')">
|
|
<!-- Download wash certificate card -->
|
|
<WhiteBoxCard :force-state="true" :defaultOpen="true" :toggleable="false" :loading="false">
|
|
<template #header>
|
|
<div class="card-header-title" :class="classes.title">
|
|
{{ $t('user_home.download_certificates') }}
|
|
</div>
|
|
<div class="card-header-icon">
|
|
<i class="fas fa-file-download"></i>
|
|
</div>
|
|
</template>
|
|
<template #content>
|
|
<div class="identical-height-spacer" :style="{ minHeight: cardMinHeight + 'px' }">
|
|
{{ $t('user_home.download_certificates_desc') }}
|
|
</div>
|
|
<!--<b-loading :is-full-page="false" :model-value="true" :can-cancel="true"/>-->
|
|
</template>
|
|
<template #footer>
|
|
<div class="card-footer-item">
|
|
<router-link to="/user/orders" class="button is-link is-fullwidth" :class="classes.button">
|
|
{{ $t('user_home.download_certificates') }}
|
|
</router-link>
|
|
</div>
|
|
</template>
|
|
</WhiteBoxCard>
|
|
</div>
|
|
<div class="column" :class="classes.column" v-if="SessionUser.hasPermission('user') || SessionUser.hasPermission('user_invoices')">
|
|
<!-- View invoices card -->
|
|
<WhiteBoxCard :force-state="true" :defaultOpen="true" :toggleable="false" :loading="false">
|
|
<template #header>
|
|
<div class="card-header-title" :class="classes.title">
|
|
{{ $t('user_home.download_invoices') }}
|
|
</div>
|
|
<div class="card-header-icon">
|
|
<i class="fas fa-file-invoice-dollar"></i>
|
|
</div>
|
|
</template>
|
|
<template #content>
|
|
<div class="identical-height-spacer" :style="{ minHeight: cardMinHeight + 'px' }">
|
|
{{ $t('user_home.download_invoices_desc') }}
|
|
</div>
|
|
<!--<b-loading :is-full-page="false" :model-value="true" :can-cancel="true"/>-->
|
|
</template>
|
|
<template #footer>
|
|
<div class="card-footer-item">
|
|
<router-link to="/user/invoices" class="button is-link is-fullwidth" :class="classes.button">
|
|
{{ $t('user_home.download_invoices') }}
|
|
</router-link>
|
|
</div>
|
|
</template>
|
|
</WhiteBoxCard>
|
|
</div>
|
|
<div class="column is-12">
|
|
<span class="is-size-6 has-text-weight-bold">
|
|
{{ $t('user_home.contact_info_prefix') }} <a href="tel:+4543717886"><span class="has-text-link">43 71 78 86</span></a> {{ $t('user_home.contact_info_or_email') }} <a href="mailto:cph@truckwash.dk"><span class="has-text-link">cph@truckwash.dk</span></a>.
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<!--<b-loading
|
|
:is-full-page="true"
|
|
:model-value="true"
|
|
:can-cancel="true"
|
|
></b-loading>-->
|
|
<!--<Home />-->
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.is-auto-size-text {
|
|
display: inline-block;
|
|
white-space: nowrap;
|
|
max-width: 100%;
|
|
vertical-align: top;
|
|
padding-right: 0.5rem;
|
|
padding-left: 0.5rem;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
</style> |