"Add tablet-specific header component and responsive layout updates for improved device compatibility."
This commit is contained in:
@@ -418,11 +418,42 @@ export const SessionUser = {
|
||||
functions: {
|
||||
/** Device type checks */
|
||||
device: {
|
||||
dimensions: {
|
||||
// Using breakpoints in accordance with Bulma CSS framework
|
||||
// https://bulma.io/documentation/start/responsiveness/#breakpoints
|
||||
mobileMaxWidth: 768,
|
||||
tabletMinWidth: 769,
|
||||
tabletMaxWidth: 1023,
|
||||
desktopMinWidth: 1024,
|
||||
desktopMaxWidth: 1215,
|
||||
widescreenMinWidth: 1216,
|
||||
widescreenMaxWidth: 1407,
|
||||
fullhdMinWidth: 1408,
|
||||
// Custom additions
|
||||
fullhdMaxWidth: 1919,
|
||||
ultrawideMinWidth: 1920,
|
||||
ultrawideMaxWidth: 3840,
|
||||
},
|
||||
isMobile: () => {
|
||||
return /Mobi|Android/i.test(navigator.userAgent);
|
||||
return windowInnerWidth.value < SessionUser.functions.device.dimensions.tabletMinWidth;
|
||||
//return /Mobi|Android/i.test(navigator.userAgent);
|
||||
},
|
||||
isDesktop: () => {
|
||||
return !/Mobi|Android/i.test(navigator.userAgent);
|
||||
return windowInnerWidth.value >= SessionUser.functions.device.dimensions.desktopMinWidth;
|
||||
//return !/Mobi|Android/i.test(navigator.userAgent);
|
||||
},
|
||||
isTablet: () => {
|
||||
return windowInnerWidth.value >= SessionUser.functions.device.dimensions.tabletMinWidth && windowInnerWidth.value <= SessionUser.functions.device.dimensions.tabletMaxWidth;
|
||||
//return /Tablet|iPad/i.test(navigator.userAgent);
|
||||
},
|
||||
isWidescreen: () => {
|
||||
return windowInnerWidth.value >= SessionUser.functions.device.dimensions.widescreenMinWidth && windowInnerWidth.value <= SessionUser.functions.device.dimensions.widescreenMaxWidth;
|
||||
},
|
||||
isFullHD: () => {
|
||||
return windowInnerWidth.value >= SessionUser.functions.device.dimensions.fullhdMinWidth;
|
||||
},
|
||||
isUltraWideScreen: () => {
|
||||
return windowInnerWidth.value >= SessionUser.functions.device.dimensions.ultrawideMinWidth;
|
||||
},
|
||||
},
|
||||
/** Check if the user has a token */
|
||||
@@ -705,6 +736,13 @@ export const SessionUser = {
|
||||
// Check if this is the latest version of the PWA
|
||||
}
|
||||
};
|
||||
|
||||
const windowInnerWidth = ref(window.innerWidth);
|
||||
const updateWindowInnerWidth = () => {
|
||||
windowInnerWidth.value = window.innerWidth;
|
||||
};
|
||||
window.addEventListener('resize', updateWindowInnerWidth);
|
||||
|
||||
export default SessionUser;
|
||||
|
||||
</script>
|
||||
@@ -5,6 +5,7 @@ import ViewportContent from "@/components/viewport/page/content/ViewportContent.
|
||||
import { backgroundColor } from "./page/headers/ViewportHeaderSettings.vue";
|
||||
import DesktopNavigationBuefy from "@/components/viewport/page/headers/DesktopNavigationBuefy.vue";
|
||||
import { ref, onMounted, computed } from "vue";
|
||||
import SessionUser from "@/components/session/token/SessionUser.vue";
|
||||
console.log("Viewport component loaded");
|
||||
|
||||
const headerHeight = ref(60); // Default header height in pixels
|
||||
@@ -35,12 +36,12 @@ onMounted(() => {
|
||||
<div class="fixed-grid has-12-cols">
|
||||
<!-- Navigation and content wrapper -->
|
||||
<div class="grid">
|
||||
<div class="cell is-col-span-2-desktop is-hidden-mobile">
|
||||
<div class="cell is-col-span-2-desktop is-hidden-mobile is-col-span-3-tablet" :class="{'is-col-span-1-widescreen': SessionUser.functions.device.isUltraWideScreen()}">
|
||||
<div class="independent-scroll slick-scroll-layout" :style="{ maxHeight: independentScrollMaxHeight, minHeight: independentScrollMaxHeight }">
|
||||
<DesktopNavigationBuefy />
|
||||
</div>
|
||||
</div>
|
||||
<div class="cell is-col-span-10-desktop is-col-span-12-mobile">
|
||||
<div class="cell is-col-span-10-desktop is-col-span-12-mobile is-col-span-9-tablet" :class="{'is-col-span-11-widescreen': SessionUser.functions.device.isUltraWideScreen()}">
|
||||
<div class="independent-scroll slick-scroll-layout" :style="{ maxHeight: independentScrollMaxHeight }">
|
||||
<ViewportContent>
|
||||
<slot />
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
<script setup lang="ts">
|
||||
import { toggleExpanded, isOpen, getDepartmentSelectionVisibility } from '@/components/viewport/page/headers/ViewportHeaderSettings.vue';
|
||||
import NavigationMenuDepartment from "@/components/viewport/page/headers/menu/NavigationMenuDepartment.vue";
|
||||
import NavigationMenuGlobalSearch from "@/components/viewport/page/headers/menu/NavigationMenuGlobalSearch.vue";
|
||||
import { IS_DEV } from '@/main';
|
||||
import SessionUser from "@/components/session/token/SessionUser.vue";
|
||||
import { useRoute } from "vue-router";
|
||||
const route = useRoute();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Company Logo | Spacer | Burger Menu -->
|
||||
<!-- The burger menu is used to toggle the mobile navigation menu -->
|
||||
<nav class="navbar is-white is-mobile is-fixed-top mobile-header-section" role="navigation" aria-label="main navigation" style="z-index: 1000;">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item is-align-content-center" href="/">
|
||||
<img src="@/assets/branding/truckwash-banner-white-compressed.png"
|
||||
alt="Truckwash Logo"
|
||||
:class="{'transform-color-black': !IS_DEV, 'transform-color-red': IS_DEV}"
|
||||
>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
<div class="navbar-start">
|
||||
<!-- Global search -->
|
||||
<div class="navbar-item" style="width: 100%;" v-show="false">
|
||||
<NavigationMenuGlobalSearch style="width: 100%;"/>
|
||||
</div>
|
||||
<!-- Department selection -->
|
||||
<div class="navbar-item" v-show="getDepartmentSelectionVisibility()">
|
||||
<NavigationMenuDepartment/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Spacer to push user account to the right -->
|
||||
<div class="navbar-item" style="flex-grow: 1;"></div>
|
||||
<div class="navbar-end">
|
||||
<!-- User account name and avatar (Desktop) -->
|
||||
<div class="navbar-item mr-1">
|
||||
<div class="buttons">
|
||||
<!-- Back-Office button -->
|
||||
<router-link class="button is-white" to="/superuser" v-if="SessionUser.canAccessSuperUser() && !route.path.includes('/superuser')">
|
||||
<span class="icon">
|
||||
<i class="fas fa-tools" :class="{'transform-color-black': !IS_DEV, 'transform-color-red': IS_DEV}"></i>
|
||||
</span>
|
||||
<span>Backoffice</span>
|
||||
</router-link>
|
||||
<!-- Department button -->
|
||||
<router-link class="button is-white" to="/admin" v-if="SessionUser.canAccessAdmin() && !route.path.includes('/admin')">
|
||||
<span class="icon">
|
||||
<i class="fas fa-building" :class="{'transform-color-black': !IS_DEV, 'transform-color-red': IS_DEV}"></i>
|
||||
</span>
|
||||
<span>Afdelinger</span>
|
||||
</router-link>
|
||||
<!-- Return to own account if impersonating -->
|
||||
<template v-if="SessionUser.hasSuperUserToken()">
|
||||
<router-link class="button is-white" to="/auth/return">
|
||||
<span class="icon">
|
||||
<i class="fas fa-undo-alt" :class="{'transform-color-black': !IS_DEV, 'transform-color-red': IS_DEV}"></i>
|
||||
</span>
|
||||
<span>Tilbage til egen konto</span>
|
||||
</router-link>
|
||||
</template>
|
||||
<router-link class="button is-white" to="/user/profile" v-if="SessionUser.authenticated.value">
|
||||
<span class="icon">
|
||||
<i class="fas fa-user-circle" :class="{'transform-color-black': !IS_DEV, 'transform-color-red': IS_DEV}"></i>
|
||||
</span>
|
||||
<span :class="{'has-text-black': !IS_DEV, 'has-text-red': IS_DEV}">{{ SessionUser.authenticated.value ? SessionUser.getName() : ''}}</span>
|
||||
</router-link>
|
||||
<template v-else>
|
||||
<router-link class="button is-white" to="/register">
|
||||
<span class="icon">
|
||||
<i class="fas fa-user-plus" :class="{'transform-color-black': !IS_DEV, 'transform-color-red': IS_DEV}"></i>
|
||||
</span>
|
||||
<span :class="{'has-text-black': !IS_DEV, 'has-text-red': IS_DEV}">Opret bruger</span>
|
||||
</router-link>
|
||||
<router-link class="button is-white" to="/login">
|
||||
<span class="icon">
|
||||
<i class="fas fa-sign-in-alt" :class="{'transform-color-black': !IS_DEV, 'transform-color-red': IS_DEV}"></i>
|
||||
</span>
|
||||
<span :class="{'has-text-black': !IS_DEV, 'has-text-red': IS_DEV}">Log ind</span>
|
||||
</router-link>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<!-- Spacer (To avoid content being hidden behind the fixed navbar) -->
|
||||
<div class="navbar-spacer"></div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.navbar-brand {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.mobile-header-section {
|
||||
height: 80px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: white;
|
||||
}
|
||||
.mobile-navbar-burger {
|
||||
border: 2px solid #f1f1f1;
|
||||
background-color: #f1f1f1;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
.navbar-spacer {
|
||||
height: 80px; /* Same height as the navbar */
|
||||
min-width: 100%;
|
||||
min-height: 80px; /* Ensures the spacer is always at least as tall as the navbar */
|
||||
background-color: transparent; /* Transparent to avoid visual interference */
|
||||
position: relative; /* Allows for proper stacking context */
|
||||
}
|
||||
.transform-color-black {
|
||||
filter: invert(100%) grayscale(100%) brightness(0) contrast(100%);
|
||||
}
|
||||
.transform-color-red {
|
||||
/* Invert colors and apply a red tint */
|
||||
filter: invert(27%) sepia(96%) saturate(7493%) hue-rotate(357deg) brightness(103%) contrast(101%);
|
||||
}
|
||||
.transform-color-warning {
|
||||
/* Invert colors and apply a yellow tint */
|
||||
filter: invert(79%) sepia(37%) saturate(7473%) hue-rotate(200deg) brightness(103%) contrast(101%);
|
||||
}
|
||||
.navbar-burger {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: #f1f1f1; /* Make the burger button background transparent */
|
||||
}
|
||||
.navbar-burger-bar {
|
||||
background-color: #000; /* Color of the burger lines */
|
||||
margin-top: 4px; /* Space between the lines */
|
||||
width: 20px; /* Width of the burger lines */
|
||||
height: 2px; /* Height of the burger lines */
|
||||
transition: background-color 0.3s ease; /* Smooth transition for color changes */
|
||||
}
|
||||
.navbar-burger-bar:nth-child(2) {
|
||||
/* This is the center line of the burger icon */
|
||||
width: 16px; /* Make the center line shorter */
|
||||
background-color: transparent; /* Make it invisible */
|
||||
}
|
||||
</style>
|
||||
@@ -3,6 +3,7 @@ import ViewportResponsiveWrapper from "@/components/viewport/conditions/elements
|
||||
import MobileHeader from "@/components/viewport/page/headers/MobileHeader.vue";
|
||||
import DesktopHeader from "@/components/viewport/page/headers/DesktopHeader.vue";
|
||||
import { isHidden } from "./ViewportHeaderSettings.vue";
|
||||
import TabletHeader from "@/components/viewport/page/headers/TabletHeader.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -13,6 +14,9 @@ import { isHidden } from "./ViewportHeaderSettings.vue";
|
||||
<template #desktop>
|
||||
<DesktopHeader />
|
||||
</template>
|
||||
<template #tablet>
|
||||
<TabletHeader />
|
||||
</template>
|
||||
</ViewportResponsiveWrapper>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -35,6 +35,62 @@ const cleanup = () => {
|
||||
onUnmounted(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
const isTablet = computed(() => {
|
||||
return SessionUser.functions.device.isTablet();
|
||||
});
|
||||
|
||||
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>
|
||||
@@ -45,7 +101,7 @@ onUnmounted(() => {
|
||||
Velkommen {{ SessionUser.getName() }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="column is-half-desktop is-hidden-desktop">
|
||||
<div class="column is-half-desktop is-hidden-desktop is-12-tablet">
|
||||
<!-- Start wash card -->
|
||||
<WhiteBoxCard :force-state="true" :defaultOpen="true" :toggleable="false" :loading="false">
|
||||
<template #header>
|
||||
@@ -70,11 +126,11 @@ onUnmounted(() => {
|
||||
</template>
|
||||
</WhiteBoxCard>
|
||||
</div>
|
||||
<div class="column is-half-desktop">
|
||||
<div class="column" :class="classes.column">
|
||||
<!-- Book wash card -->
|
||||
<WhiteBoxCard :force-state="true" :defaultOpen="true" :toggleable="false" :loading="false">
|
||||
<template #header>
|
||||
<div class="card-header-title">
|
||||
<div class="card-header-title" :class="classes.title">
|
||||
Book vask
|
||||
</div>
|
||||
<div class="card-header-icon">
|
||||
@@ -89,18 +145,18 @@ onUnmounted(() => {
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="card-footer-item">
|
||||
<router-link to="/user/bookings/book" class="button is-link is-fullwidth">
|
||||
<router-link to="/user/bookings/book" class="button is-link is-fullwidth" :class="classes.button">
|
||||
Book vask
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
</WhiteBoxCard>
|
||||
</div>
|
||||
<div class="column is-half-desktop">
|
||||
<div class="column" :class="classes.column">
|
||||
<!-- Add vehicle card -->
|
||||
<WhiteBoxCard :force-state="true" :defaultOpen="true" :toggleable="false" :loading="false">
|
||||
<template #header>
|
||||
<div class="card-header-title">
|
||||
<div class="card-header-title" :class="classes.title">
|
||||
Opret køretøj
|
||||
</div>
|
||||
<div class="card-header-icon">
|
||||
@@ -115,18 +171,18 @@ onUnmounted(() => {
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="card-footer-item">
|
||||
<router-link to="/user/vehicles" class="button is-link is-fullwidth">
|
||||
<router-link to="/user/vehicles" class="button is-link is-fullwidth" :class="classes.button">
|
||||
Opret køretøj
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
</WhiteBoxCard>
|
||||
</div>
|
||||
<div class="column is-half-desktop">
|
||||
<div class="column" :class="classes.column">
|
||||
<!-- Download wash certificate card -->
|
||||
<WhiteBoxCard :force-state="true" :defaultOpen="true" :toggleable="false" :loading="false">
|
||||
<template #header>
|
||||
<div class="card-header-title">
|
||||
<div class="card-header-title" :class="classes.title">
|
||||
Download vaskecertifikater
|
||||
</div>
|
||||
<div class="card-header-icon">
|
||||
@@ -141,18 +197,18 @@ onUnmounted(() => {
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="card-footer-item">
|
||||
<router-link to="/user/wash-certificates" class="button is-link is-fullwidth" disabled="true">
|
||||
<router-link to="/user/wash-certificates" class="button is-link is-fullwidth" :class="classes.button" disabled="true">
|
||||
Download vaskecertifikater
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
</WhiteBoxCard>
|
||||
</div>
|
||||
<div class="column is-half-desktop">
|
||||
<div class="column" :class="classes.column">
|
||||
<!-- View invoices card -->
|
||||
<WhiteBoxCard :force-state="true" :defaultOpen="true" :toggleable="false" :loading="false">
|
||||
<template #header>
|
||||
<div class="card-header-title">
|
||||
<div class="card-header-title" :class="classes.title">
|
||||
Download fakturaer
|
||||
</div>
|
||||
<div class="card-header-icon">
|
||||
@@ -167,7 +223,7 @@ onUnmounted(() => {
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="card-footer-item">
|
||||
<router-link to="/user/invoices" class="button is-link is-fullwidth">
|
||||
<router-link to="/user/invoices" class="button is-link is-fullwidth" :class="classes.button">
|
||||
Download fakturaer
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user