- Full implementation of tab bar next to logo in mobile client
- Customisation option in Tenant Make - Splitting all controls in the newly created tabs
This commit is contained in:
@@ -1,38 +1,106 @@
|
||||
active_text_color<template>
|
||||
<div class="chat-app-container">
|
||||
<!-- Message History - takes available space -->
|
||||
<message-history
|
||||
:messages="displayMessages"
|
||||
:is-typing="isTyping"
|
||||
:is-submitting-form="isSubmittingForm"
|
||||
:api-prefix="apiPrefix"
|
||||
:auto-scroll="true"
|
||||
@specialist-error="handleSpecialistError"
|
||||
@specialist-complete="handleSpecialistComplete"
|
||||
ref="messageHistory"
|
||||
class="chat-messages-area"
|
||||
></message-history>
|
||||
<!-- Desktop layout: huidige gedrag behouden -->
|
||||
<div v-if="!isMobile" class="chat-desktop-layout">
|
||||
<message-history
|
||||
:messages="displayMessages"
|
||||
:is-typing="isTyping"
|
||||
:is-submitting-form="isSubmittingForm"
|
||||
:api-prefix="apiPrefix"
|
||||
:auto-scroll="true"
|
||||
@specialist-error="handleSpecialistError"
|
||||
@specialist-complete="handleSpecialistComplete"
|
||||
ref="messageHistory"
|
||||
class="chat-messages-area"
|
||||
></message-history>
|
||||
|
||||
<!-- Chat Input - to the bottom -->
|
||||
<chat-input
|
||||
:current-message="currentMessage"
|
||||
:is-loading="isLoading"
|
||||
:max-length="2000"
|
||||
:allow-file-upload="true"
|
||||
:allow-voice-message="false"
|
||||
:form-data="currentInputFormData"
|
||||
:active-ai-message="activeAiMessage"
|
||||
:api-prefix="apiPrefix"
|
||||
@send-message="sendMessage"
|
||||
@update-message="updateCurrentMessage"
|
||||
@upload-file="handleFileUpload"
|
||||
@record-voice="handleVoiceRecord"
|
||||
@submit-form="submitFormFromInput"
|
||||
@specialist-error="handleSpecialistError"
|
||||
@specialist-complete="handleSpecialistComplete"
|
||||
ref="chatInput"
|
||||
class="chat-input-area"
|
||||
></chat-input>
|
||||
<chat-input
|
||||
:current-message="currentMessage"
|
||||
:is-loading="isLoading"
|
||||
:max-length="2000"
|
||||
:allow-file-upload="true"
|
||||
:allow-voice-message="false"
|
||||
:form-data="currentInputFormData"
|
||||
:active-ai-message="activeAiMessage"
|
||||
:api-prefix="apiPrefix"
|
||||
@send-message="sendMessage"
|
||||
@update-message="updateCurrentMessage"
|
||||
@upload-file="handleFileUpload"
|
||||
@record-voice="handleVoiceRecord"
|
||||
@submit-form="submitFormFromInput"
|
||||
@specialist-error="handleSpecialistError"
|
||||
@specialist-complete="handleSpecialistComplete"
|
||||
ref="chatInput"
|
||||
class="chat-input-area"
|
||||
></chat-input>
|
||||
</div>
|
||||
|
||||
<!-- Mobiele layout met tabs in de header -->
|
||||
<div v-else class="chat-mobile-layout">
|
||||
<header class="chat-mobile-header">
|
||||
<div class="chat-mobile-header-left">
|
||||
<SideBarLogo
|
||||
:logo-url="tenantLogoUrl"
|
||||
:make-name="tenantName"
|
||||
/>
|
||||
</div>
|
||||
<div class="chat-mobile-header-right">
|
||||
<MobileTabBar
|
||||
v-model="activeTabId"
|
||||
:tabs="mobileTabs"
|
||||
placement="header"
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="chat-mobile-content" :class="`tab-${activeTabId}`">
|
||||
<message-history
|
||||
v-if="activeTabId === 'history'"
|
||||
:messages="displayMessages"
|
||||
:is-typing="isTyping"
|
||||
:is-submitting-form="isSubmittingForm"
|
||||
:api-prefix="apiPrefix"
|
||||
:auto-scroll="true"
|
||||
@specialist-error="handleSpecialistError"
|
||||
@specialist-complete="handleSpecialistComplete"
|
||||
ref="messageHistory"
|
||||
class="chat-messages-area"
|
||||
></message-history>
|
||||
|
||||
<chat-input
|
||||
v-else-if="activeTabId === 'chat'"
|
||||
:current-message="currentMessage"
|
||||
:is-loading="isLoading"
|
||||
:max-length="settings.maxMessageLength"
|
||||
:allow-file-upload="settings.allowFileUpload"
|
||||
:allow-voice-message="settings.allowVoiceMessage"
|
||||
:form-data="currentInputFormData"
|
||||
:active-ai-message="activeAiMessage"
|
||||
:api-prefix="apiPrefix"
|
||||
@send-message="sendMessage"
|
||||
@update-message="updateCurrentMessage"
|
||||
@upload-file="handleFileUpload"
|
||||
@record-voice="handleVoiceRecord"
|
||||
@submit-form="submitFormFromInput"
|
||||
@specialist-error="handleSpecialistError"
|
||||
@specialist-complete="handleSpecialistComplete"
|
||||
ref="chatInput"
|
||||
class="chat-input-area tab-chat-input"
|
||||
></chat-input>
|
||||
|
||||
<SideBarMobileSetup
|
||||
v-else-if="activeTabId === 'setup'"
|
||||
:tenant-make="{ name: tenantName, subtitle: tenantSubtitle }"
|
||||
:explanation-text="originalExplanation"
|
||||
:initial-language="currentLanguage"
|
||||
:current-language="currentLanguage"
|
||||
:supported-language-details="supportedLanguageDetails"
|
||||
:allowed-languages="allowedLanguages"
|
||||
:api-prefix="apiPrefix"
|
||||
@language-changed="handleLanguageChangedFromSetup"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content Modal - positioned at ChatApp level -->
|
||||
<content-modal
|
||||
@@ -60,12 +128,16 @@ import ProgressTracker from './ProgressTracker.vue';
|
||||
import LanguageSelector from './LanguageSelector.vue';
|
||||
import ChatInput from './ChatInput.vue';
|
||||
import ContentModal from './ContentModal.vue';
|
||||
import SideBarLogo from './SideBarLogo.vue';
|
||||
import MobileTabBar from './MobileTabBar.vue';
|
||||
import SideBarMobileSetup from './SideBarMobileSetup.vue';
|
||||
|
||||
// Import language provider
|
||||
import { createLanguageProvider, LANGUAGE_PROVIDER_KEY } from '../js/services/LanguageProvider.js';
|
||||
// Import content modal composable
|
||||
import { provideContentModal } from '../js/composables/useContentModal.js';
|
||||
import { provide } from 'vue';
|
||||
import useChatViewport from '../js/composables/useChatViewport.js';
|
||||
|
||||
export default {
|
||||
name: 'ChatApp',
|
||||
@@ -77,7 +149,10 @@ export default {
|
||||
MessageHistory,
|
||||
ProgressTracker,
|
||||
ChatInput,
|
||||
ContentModal
|
||||
ContentModal,
|
||||
SideBarLogo,
|
||||
MobileTabBar,
|
||||
SideBarMobileSetup
|
||||
},
|
||||
|
||||
setup() {
|
||||
@@ -90,13 +165,17 @@ export default {
|
||||
|
||||
// Creëer en provide content modal
|
||||
const contentModal = provideContentModal();
|
||||
|
||||
|
||||
// Initialiseer viewport-/device-informatie via useChatViewport (VueUse)
|
||||
const { isMobile } = useChatViewport();
|
||||
|
||||
// Provide aan alle child components
|
||||
provide(LANGUAGE_PROVIDER_KEY, languageProvider);
|
||||
|
||||
return {
|
||||
languageProvider,
|
||||
contentModal
|
||||
contentModal,
|
||||
isMobile
|
||||
};
|
||||
},
|
||||
|
||||
@@ -111,6 +190,7 @@ export default {
|
||||
return {
|
||||
// Tenant info
|
||||
tenantName: tenantMake.name || 'EveAI',
|
||||
tenantSubtitle: tenantMake.subtitle || '',
|
||||
tenantLogoUrl: tenantMake.logo_url || '',
|
||||
|
||||
// Taal gerelateerde data
|
||||
@@ -147,10 +227,13 @@ export default {
|
||||
autoScroll: settings.autoScroll === true
|
||||
},
|
||||
|
||||
// UI state
|
||||
isMobile: window.innerWidth <= 768,
|
||||
// UI state (fallback flags voor oudere logica)
|
||||
isMobileFallback: window.innerWidth <= 768,
|
||||
showSidebar: window.innerWidth > 768,
|
||||
|
||||
// Mobile tab state
|
||||
activeTabId: 'chat',
|
||||
|
||||
// Advanced features
|
||||
messageSearch: '',
|
||||
filteredMessages: [],
|
||||
@@ -193,16 +276,57 @@ export default {
|
||||
return this.supportedLanguages.filter(lang =>
|
||||
this.allowedLanguages.includes(lang.code)
|
||||
);
|
||||
},
|
||||
|
||||
mobileTabs() {
|
||||
return [
|
||||
{
|
||||
id: 'chat',
|
||||
iconName: 'chat',
|
||||
label: 'Chat'
|
||||
},
|
||||
{
|
||||
id: 'history',
|
||||
iconName: 'history',
|
||||
label: 'Historiek'
|
||||
},
|
||||
{
|
||||
id: 'setup',
|
||||
iconName: 'settings',
|
||||
label: 'Setup'
|
||||
}
|
||||
];
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.initializeChat();
|
||||
this.setupEventListeners();
|
||||
|
||||
// Stel initiale actieve tab in (optioneel via config)
|
||||
const defaultTab = (window.chatConfig && window.chatConfig.defaultTab) || 'chat';
|
||||
if (this.mobileTabs.find(t => t.id === defaultTab)) {
|
||||
this.activeTabId = defaultTab;
|
||||
}
|
||||
|
||||
// Luister naar globale events om tab te wisselen
|
||||
this.globalTabListener = (event) => {
|
||||
const tabId = event?.detail?.tabId;
|
||||
if (!tabId) return;
|
||||
if (this.mobileTabs.find(t => t.id === tabId)) {
|
||||
this.activeTabId = tabId;
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('evie-chat-set-tab', this.globalTabListener);
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.cleanup();
|
||||
|
||||
if (this.globalTabListener) {
|
||||
document.removeEventListener('evie-chat-set-tab', this.globalTabListener);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
@@ -453,11 +577,13 @@ export default {
|
||||
}
|
||||
});
|
||||
|
||||
// Window resize listener
|
||||
window.addEventListener('resize', () => {
|
||||
this.isMobile = window.innerWidth <= 768;
|
||||
// Window resize listener voor fallback flags
|
||||
this.handleResize = () => {
|
||||
this.isMobileFallback = window.innerWidth <= 768;
|
||||
this.showSidebar = window.innerWidth > 768;
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener('resize', this.handleResize);
|
||||
},
|
||||
|
||||
cleanup() {
|
||||
@@ -516,6 +642,12 @@ export default {
|
||||
this.isLoading = false;
|
||||
},
|
||||
|
||||
handleLanguageChangedFromSetup(newLanguage) {
|
||||
// Update lokale taalstate; verdere effecten worden opgepikt door
|
||||
// bestaande global listener en LanguageProvider / chatConfig.
|
||||
this.currentLanguage = newLanguage;
|
||||
},
|
||||
|
||||
// UI helpers
|
||||
scrollToBottom() {
|
||||
if (this.$refs.messageHistory) {
|
||||
@@ -560,7 +692,7 @@ export default {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
/* height: 100%; avoided to let flex sizing control height */
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
max-width: 1000px;
|
||||
@@ -571,6 +703,71 @@ export default {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chat-mobile-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: calc(var(--safe-vh, 1vh) * 100);
|
||||
}
|
||||
|
||||
.chat-mobile-content {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Mobiele header met logo links en tabs rechts */
|
||||
.chat-mobile-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
justify-content: space-between;
|
||||
padding: 6px 8px;
|
||||
background: var(--tab-background, #0a0a0a);
|
||||
}
|
||||
|
||||
.chat-mobile-header-left {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
min-width: 56px; /* Zorg dat logo altijd minstens vierkant kan tonen */
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.chat-mobile-header-right {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Specifieke layout voor de chat-tab: inputblok onderaan */
|
||||
.chat-mobile-content.tab-chat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tab-chat-input {
|
||||
margin-top: auto;
|
||||
flex-shrink: 0;
|
||||
/* Iets meer visuele marge boven de onderrand, maar nog steeds rekening houden met safe inset */
|
||||
padding-bottom: calc(6px + var(--safe-bottom-inset, 0px));
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.chat-app-container {
|
||||
/* Minder padding op mobiel zodat de tabbar binnen de viewport valt */
|
||||
padding: 8px 8px 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-messages-area {
|
||||
flex: 1;
|
||||
min-height: 0; /* ensure child can scroll */
|
||||
|
||||
Reference in New Issue
Block a user