597 lines
32 KiB
JavaScript
597 lines
32 KiB
JavaScript
import { authState, checkAuth, login, logout } from '/mobile/shared/auth.js';
|
||
import LoginScreen from '/mobile/components/LoginScreen.js';
|
||
import MainMenu from '/mobile/components/MainMenu.js';
|
||
import LagerModule from '/mobile/modules/lager/LagerModule.js';
|
||
import ShippingNoteModule from '/mobile/modules/lager/shippingnote/ShippingNoteModule.js';
|
||
import WorkorderModule from '/mobile/modules/workorder/WorkorderModule.js';
|
||
|
||
const { createApp, ref, computed, onMounted, onUnmounted, watch } = Vue;
|
||
|
||
const isPWAInstalled = () => {
|
||
if (window.matchMedia('(display-mode: standalone)').matches) return true;
|
||
if (window.navigator.standalone === true) return true;
|
||
if (document.referrer.includes('android-app://')) return true;
|
||
return false;
|
||
};
|
||
|
||
const shouldRequirePWA = () => {
|
||
return window.location.hostname === 'thetool.xinon.at';
|
||
};
|
||
|
||
const parseInitialRoute = () => {
|
||
const initialPath = window.TT_CONFIG?.INITIAL_PATH || '/MobileApp';
|
||
const parts = initialPath.replace('/MobileApp', '').split('/').filter(Boolean);
|
||
return {
|
||
module: parts[0] || null,
|
||
submodule: parts[1] || null
|
||
};
|
||
};
|
||
|
||
const App = {
|
||
components: {
|
||
LoginScreen,
|
||
MainMenu,
|
||
LagerModule,
|
||
ShippingNoteModule,
|
||
WorkorderModule
|
||
},
|
||
|
||
setup() {
|
||
const currentView = ref('loading');
|
||
const user = ref(null);
|
||
const toast = ref({ show: false, message: '', type: 'success' });
|
||
const theme = ref('system');
|
||
const showSettings = ref(false);
|
||
const lagerSimpleMode = ref(false);
|
||
const currentModule = ref(null);
|
||
const currentSubmodule = ref(null);
|
||
const lastWorkflow = ref(null);
|
||
const showContinuePrompt = ref(false);
|
||
const showInstallPrompt = ref(false);
|
||
const deferredInstallPrompt = ref(null);
|
||
const isIOS = ref(/iPad|iPhone|iPod/.test(navigator.userAgent));
|
||
const isAndroid = ref(/Android/.test(navigator.userAgent));
|
||
const canGoBack = computed(() => currentModule.value !== null);
|
||
|
||
const applyTheme = () => {
|
||
const isDark = localStorage.theme === 'dark' ||
|
||
(!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||
document.documentElement.classList.toggle('dark', isDark);
|
||
|
||
const metaThemeColor = document.querySelector('meta[name="theme-color"]');
|
||
if (metaThemeColor) {
|
||
metaThemeColor.setAttribute('content', isDark ? '#0f172a' : '#005384');
|
||
}
|
||
};
|
||
|
||
const setTheme = (newTheme) => {
|
||
theme.value = newTheme;
|
||
if (newTheme === 'system') {
|
||
localStorage.removeItem('theme');
|
||
} else {
|
||
localStorage.setItem('theme', newTheme);
|
||
}
|
||
applyTheme();
|
||
};
|
||
|
||
const handleInstallPrompt = (e) => {
|
||
e.preventDefault();
|
||
deferredInstallPrompt.value = e;
|
||
};
|
||
|
||
const triggerInstall = async () => {
|
||
if (!deferredInstallPrompt.value) return;
|
||
deferredInstallPrompt.value.prompt();
|
||
const { outcome } = await deferredInstallPrompt.value.userChoice;
|
||
if (outcome === 'accepted') {
|
||
showInstallPrompt.value = false;
|
||
window.location.reload();
|
||
}
|
||
deferredInstallPrompt.value = null;
|
||
};
|
||
|
||
const loadLagerSettings = () => {
|
||
try {
|
||
const saved = localStorage.getItem('movement_settings');
|
||
if (saved) {
|
||
const settings = JSON.parse(saved);
|
||
lagerSimpleMode.value = settings.simpleMode || false;
|
||
}
|
||
} catch (e) {}
|
||
};
|
||
|
||
const setLagerSimpleMode = (value) => {
|
||
lagerSimpleMode.value = value;
|
||
try {
|
||
const saved = localStorage.getItem('movement_settings');
|
||
const settings = saved ? JSON.parse(saved) : {};
|
||
settings.simpleMode = value;
|
||
localStorage.setItem('movement_settings', JSON.stringify(settings));
|
||
} catch (e) {}
|
||
};
|
||
|
||
const saveLastWorkflow = (module, submodule) => {
|
||
if (module) {
|
||
const workflow = { module, submodule: submodule || null, timestamp: Date.now() };
|
||
localStorage.setItem('lastWorkflow', JSON.stringify(workflow));
|
||
lastWorkflow.value = workflow;
|
||
}
|
||
};
|
||
|
||
const loadLastWorkflow = () => {
|
||
try {
|
||
const saved = localStorage.getItem('lastWorkflow');
|
||
if (saved) {
|
||
const workflow = JSON.parse(saved);
|
||
if (Date.now() - workflow.timestamp < 24 * 60 * 60 * 1000) {
|
||
return workflow;
|
||
}
|
||
}
|
||
} catch (e) {}
|
||
return null;
|
||
};
|
||
|
||
const navigate = (module, submodule = null) => {
|
||
currentModule.value = module;
|
||
currentSubmodule.value = submodule;
|
||
showContinuePrompt.value = false;
|
||
saveLastWorkflow(module, submodule);
|
||
let path = '/MobileApp';
|
||
if (module) path += '/' + module;
|
||
if (submodule) path += '/' + submodule;
|
||
history.pushState({ module, submodule }, '', path);
|
||
};
|
||
|
||
const continueLastWorkflow = () => {
|
||
if (lastWorkflow.value) {
|
||
navigate(lastWorkflow.value.module, lastWorkflow.value.submodule);
|
||
}
|
||
};
|
||
|
||
const dismissContinuePrompt = () => {
|
||
showContinuePrompt.value = false;
|
||
};
|
||
|
||
const goHome = () => {
|
||
navigate(null, null);
|
||
};
|
||
|
||
const goBack = () => {
|
||
if (currentSubmodule.value) {
|
||
navigate(currentModule.value, null);
|
||
} else if (currentModule.value) {
|
||
navigate(null, null);
|
||
}
|
||
};
|
||
|
||
const handlePopstate = (event) => {
|
||
if (event.state) {
|
||
currentModule.value = event.state.module;
|
||
currentSubmodule.value = event.state.submodule;
|
||
} else {
|
||
currentModule.value = null;
|
||
currentSubmodule.value = null;
|
||
}
|
||
};
|
||
|
||
const handleLogin = async (credentials) => {
|
||
if (credentials._2faSuccess) {
|
||
user.value = credentials.user;
|
||
currentView.value = 'app';
|
||
showToast('Erfolgreich angemeldet', 'success');
|
||
return { success: true };
|
||
}
|
||
|
||
const result = await login(credentials);
|
||
if (result.success) {
|
||
user.value = result.user;
|
||
currentView.value = 'app';
|
||
showToast('Erfolgreich angemeldet', 'success');
|
||
}
|
||
return result;
|
||
};
|
||
|
||
const handleLogout = async () => {
|
||
await logout();
|
||
user.value = null;
|
||
currentModule.value = null;
|
||
currentSubmodule.value = null;
|
||
currentView.value = 'login';
|
||
showToast('Abgemeldet', 'success');
|
||
};
|
||
|
||
const showToast = (message, type = 'success') => {
|
||
toast.value = { show: true, message, type };
|
||
setTimeout(() => { toast.value.show = false; }, 3000);
|
||
};
|
||
|
||
const currentComponent = computed(() => {
|
||
if (currentView.value !== 'app') return null;
|
||
if (!currentModule.value) return 'MainMenu';
|
||
if (currentModule.value.toLowerCase() === 'lieferschein') return 'ShippingNoteModule';
|
||
if (currentModule.value.toLowerCase() === 'lager') return 'LagerModule';
|
||
if (currentModule.value.toLowerCase() === 'workorder') return 'WorkorderModule';
|
||
return 'MainMenu';
|
||
});
|
||
|
||
const breadcrumbs = computed(() => {
|
||
const crumbs = [{ label: 'Home', module: null, submodule: null }];
|
||
if (currentModule.value) {
|
||
crumbs.push({ label: currentModule.value, module: currentModule.value, submodule: null });
|
||
}
|
||
if (currentSubmodule.value) {
|
||
crumbs.push({ label: currentSubmodule.value, module: currentModule.value, submodule: currentSubmodule.value });
|
||
}
|
||
return crumbs;
|
||
});
|
||
|
||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||
|
||
onMounted(async () => {
|
||
const savedTheme = localStorage.getItem('theme');
|
||
if (savedTheme) theme.value = savedTheme;
|
||
applyTheme();
|
||
mediaQuery.addEventListener('change', applyTheme);
|
||
window.addEventListener('popstate', handlePopstate);
|
||
window.addEventListener('beforeinstallprompt', handleInstallPrompt);
|
||
loadLagerSettings();
|
||
|
||
if (shouldRequirePWA() && !isPWAInstalled()) {
|
||
showInstallPrompt.value = true;
|
||
currentView.value = 'install';
|
||
return;
|
||
}
|
||
|
||
const result = await checkAuth();
|
||
if (result.authenticated) {
|
||
user.value = result.user;
|
||
currentView.value = 'app';
|
||
const initialRoute = parseInitialRoute();
|
||
currentModule.value = initialRoute.module;
|
||
currentSubmodule.value = initialRoute.submodule;
|
||
history.replaceState(
|
||
{ module: initialRoute.module, submodule: initialRoute.submodule },
|
||
'',
|
||
window.location.pathname
|
||
);
|
||
if (!initialRoute.module && !initialRoute.submodule) {
|
||
const saved = loadLastWorkflow();
|
||
if (saved) {
|
||
lastWorkflow.value = saved;
|
||
showContinuePrompt.value = true;
|
||
}
|
||
}
|
||
} else {
|
||
currentView.value = 'login';
|
||
}
|
||
});
|
||
|
||
onUnmounted(() => {
|
||
mediaQuery.removeEventListener('change', applyTheme);
|
||
window.removeEventListener('popstate', handlePopstate);
|
||
window.removeEventListener('beforeinstallprompt', handleInstallPrompt);
|
||
});
|
||
|
||
return {
|
||
currentView,
|
||
user,
|
||
toast,
|
||
theme,
|
||
showSettings,
|
||
currentModule,
|
||
currentSubmodule,
|
||
currentComponent,
|
||
canGoBack,
|
||
breadcrumbs,
|
||
handleLogin,
|
||
handleLogout,
|
||
navigate,
|
||
goHome,
|
||
goBack,
|
||
showToast,
|
||
setTheme,
|
||
lagerSimpleMode,
|
||
setLagerSimpleMode,
|
||
showInstallPrompt,
|
||
deferredInstallPrompt,
|
||
isIOS,
|
||
isAndroid,
|
||
triggerInstall,
|
||
lastWorkflow,
|
||
showContinuePrompt,
|
||
continueLastWorkflow,
|
||
dismissContinuePrompt,
|
||
};
|
||
},
|
||
|
||
template: `
|
||
<div class="relative h-full w-full bg-slate-50 dark:bg-slate-900 transition-colors duration-300">
|
||
<div v-if="currentView === 'loading'" class="flex items-center justify-center h-full">
|
||
<div class="text-center">
|
||
<img src="/assets/images/xinon-full-transparent.png" class="h-12 mx-auto mb-4 dark:hidden">
|
||
<img src="/assets/images/xinon-full-transparent-white.png" class="h-12 mx-auto mb-4 hidden dark:block">
|
||
<div class="animate-pulse text-slate-500 dark:text-slate-400">Lädt...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-else-if="currentView === 'install'" class="min-h-screen flex items-center justify-center p-4 relative overflow-hidden">
|
||
<div class="absolute inset-0 bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900">
|
||
<div class="absolute inset-0 opacity-40" style="background-image: linear-gradient(to right, rgba(0, 83, 132, 0.15) 1px, transparent 1px), linear-gradient(to bottom, rgba(0, 83, 132, 0.15) 1px, transparent 1px); background-size: 50px 50px;"></div>
|
||
<div class="absolute w-3 h-3 bg-cyan-400 rounded-full network-node" style="top: 12%; left: 18%; box-shadow: 0 0 25px 8px rgba(34, 211, 238, 0.6);"></div>
|
||
<div class="absolute w-2.5 h-2.5 bg-blue-400 rounded-full network-node" style="top: 22%; left: 78%; animation-delay: 0.5s; box-shadow: 0 0 20px 6px rgba(96, 165, 250, 0.6);"></div>
|
||
<div class="absolute w-3 h-3 bg-cyan-300 rounded-full network-node-slow" style="top: 72%; left: 12%; animation-delay: 1s; box-shadow: 0 0 25px 8px rgba(103, 232, 249, 0.6);"></div>
|
||
<div class="absolute w-2.5 h-2.5 bg-blue-300 rounded-full network-node" style="top: 82%; left: 85%; animation-delay: 0.3s; box-shadow: 0 0 20px 6px rgba(147, 197, 253, 0.6);"></div>
|
||
</div>
|
||
|
||
<div class="relative bg-white/95 dark:bg-slate-800/95 backdrop-blur-sm rounded-2xl shadow-2xl p-6 w-full max-w-sm border border-white/20 dark:border-slate-700/50">
|
||
<div class="mb-6">
|
||
<img src="/assets/images/xinon-full-transparent.png" class="h-14 mx-auto dark:hidden" alt="Logo">
|
||
<img src="/assets/images/xinon-full-transparent-white.png" class="h-14 mx-auto hidden dark:block" alt="Logo">
|
||
</div>
|
||
|
||
<div class="text-center mb-6">
|
||
<div class="w-16 h-16 bg-primary/10 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
||
</svg>
|
||
</div>
|
||
<h2 class="text-xl font-bold text-slate-800 dark:text-white mb-2">App installieren</h2>
|
||
<p class="text-sm text-slate-500 dark:text-slate-400">
|
||
Für die beste Erfahrung installiere die App auf deinem Gerät.
|
||
</p>
|
||
</div>
|
||
|
||
<div v-if="isAndroid && deferredInstallPrompt">
|
||
<button
|
||
@click="triggerInstall"
|
||
class="w-full py-3 px-4 bg-primary text-white font-bold rounded-xl hover:bg-primary/90 focus:ring-4 focus:ring-primary/30 transition flex items-center justify-center gap-2"
|
||
>
|
||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
||
</svg>
|
||
App installieren
|
||
</button>
|
||
</div>
|
||
|
||
<div v-else-if="isIOS" class="space-y-4">
|
||
<div class="bg-slate-50 dark:bg-slate-700/50 rounded-xl p-4">
|
||
<p class="text-sm font-medium text-slate-700 dark:text-slate-300 mb-3">So installierst du die App:</p>
|
||
<ol class="text-sm text-slate-600 dark:text-slate-400 space-y-3">
|
||
<li class="flex items-start gap-3">
|
||
<span class="flex-shrink-0 w-6 h-6 bg-primary/10 text-primary rounded-full flex items-center justify-center text-xs font-bold">1</span>
|
||
<span>Tippe auf das <strong>Teilen</strong>-Symbol
|
||
<svg xmlns="http://www.w3.org/2000/svg" class="inline h-4 w-4 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z" />
|
||
</svg>
|
||
</span>
|
||
</li>
|
||
<li class="flex items-start gap-3">
|
||
<span class="flex-shrink-0 w-6 h-6 bg-primary/10 text-primary rounded-full flex items-center justify-center text-xs font-bold">2</span>
|
||
<span>Scrolle und wähle <strong>"Zum Home-Bildschirm"</strong></span>
|
||
</li>
|
||
<li class="flex items-start gap-3">
|
||
<span class="flex-shrink-0 w-6 h-6 bg-primary/10 text-primary rounded-full flex items-center justify-center text-xs font-bold">3</span>
|
||
<span>Tippe auf <strong>"Hinzufügen"</strong></span>
|
||
</li>
|
||
</ol>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-else-if="isAndroid" class="space-y-4">
|
||
<div class="bg-slate-50 dark:bg-slate-700/50 rounded-xl p-4">
|
||
<p class="text-sm font-medium text-slate-700 dark:text-slate-300 mb-3">So installierst du die App:</p>
|
||
<ol class="text-sm text-slate-600 dark:text-slate-400 space-y-3">
|
||
<li class="flex items-start gap-3">
|
||
<span class="flex-shrink-0 w-6 h-6 bg-primary/10 text-primary rounded-full flex items-center justify-center text-xs font-bold">1</span>
|
||
<span>Tippe auf das <strong>Menü</strong> (⋮) oben rechts</span>
|
||
</li>
|
||
<li class="flex items-start gap-3">
|
||
<span class="flex-shrink-0 w-6 h-6 bg-primary/10 text-primary rounded-full flex items-center justify-center text-xs font-bold">2</span>
|
||
<span>Wähle <strong>"App installieren"</strong> oder <strong>"Zum Startbildschirm hinzufügen"</strong></span>
|
||
</li>
|
||
<li class="flex items-start gap-3">
|
||
<span class="flex-shrink-0 w-6 h-6 bg-primary/10 text-primary rounded-full flex items-center justify-center text-xs font-bold">3</span>
|
||
<span>Bestätige mit <strong>"Installieren"</strong></span>
|
||
</li>
|
||
</ol>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-else class="space-y-4">
|
||
<div class="bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-xl p-4">
|
||
<p class="text-sm text-amber-700 dark:text-amber-400">
|
||
<strong>Hinweis:</strong> Diese App ist für mobile Geräte optimiert. Bitte öffne diese Seite auf deinem Smartphone und installiere die App.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mt-4 pt-4 border-t border-slate-100 dark:border-slate-700 text-center">
|
||
<p class="text-xs text-slate-400 dark:text-slate-500">
|
||
powered by <span class="font-semibold">XINON</span>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<LoginScreen
|
||
v-else-if="currentView === 'login'"
|
||
@login="handleLogin"
|
||
:theme="theme"
|
||
@set-theme="setTheme"
|
||
/>
|
||
|
||
<template v-else-if="currentView === 'app'">
|
||
<div class="h-full flex flex-col">
|
||
<header class="bg-white dark:bg-slate-800 border-b border-slate-200 dark:border-slate-700 px-2 py-2 flex items-center safe-area-top flex-shrink-0 z-10">
|
||
<button
|
||
@click="goBack"
|
||
:class="[
|
||
'w-10 h-10 flex items-center justify-center rounded-full transition',
|
||
canGoBack
|
||
? 'text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700'
|
||
: 'text-transparent pointer-events-none'
|
||
]"
|
||
>
|
||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||
</svg>
|
||
</button>
|
||
|
||
<div class="flex-1 flex justify-center">
|
||
<img src="/assets/images/xinon-full-transparent.png" class="h-7 dark:hidden" alt="Logo">
|
||
<img src="/assets/images/xinon-full-transparent-white.png" class="h-7 hidden dark:block" alt="Logo">
|
||
</div>
|
||
|
||
<button
|
||
@click="showSettings = true"
|
||
class="w-10 h-10 flex items-center justify-center rounded-full text-slate-500 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-700 transition"
|
||
>
|
||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||
</svg>
|
||
</button>
|
||
</header>
|
||
|
||
<main class="flex-1 overflow-y-auto">
|
||
<div v-if="showContinuePrompt && !currentModule" class="p-3 pb-0">
|
||
<div class="bg-primary/10 dark:bg-primary/20 border border-primary/30 rounded-xl p-4">
|
||
<div class="flex items-center justify-between">
|
||
<div class="flex-1 min-w-0">
|
||
<p class="text-xs text-primary/70 dark:text-primary/80 font-medium uppercase tracking-wide">Fortsetzen</p>
|
||
<p class="font-semibold text-slate-800 dark:text-white truncate">
|
||
{{ lastWorkflow?.module }}<template v-if="lastWorkflow?.submodule"> › {{ lastWorkflow.submodule }}</template>
|
||
</p>
|
||
</div>
|
||
<div class="flex gap-2 ml-3">
|
||
<button @click="dismissContinuePrompt" class="p-2 text-slate-400 hover:text-slate-600 dark:hover:text-slate-300">
|
||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||
</svg>
|
||
</button>
|
||
<button @click="continueLastWorkflow" class="px-4 py-2 bg-primary text-white font-bold rounded-lg hover:bg-primary/90 transition">
|
||
Weiter
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<MainMenu
|
||
v-if="!currentModule"
|
||
:user="user"
|
||
@navigate="navigate"
|
||
/>
|
||
|
||
<LagerModule
|
||
v-else-if="currentModule?.toLowerCase() === 'lager'"
|
||
:user="user"
|
||
:submodule="currentSubmodule"
|
||
:simple-mode="lagerSimpleMode"
|
||
@navigate="navigate"
|
||
@toast="showToast"
|
||
/>
|
||
|
||
<ShippingNoteModule
|
||
v-else-if="currentModule?.toLowerCase() === 'lieferschein'"
|
||
:user="user"
|
||
@toast="showToast"
|
||
/>
|
||
|
||
<WorkorderModule
|
||
v-else-if="currentModule?.toLowerCase() === 'workorder'"
|
||
:user="user"
|
||
@navigate="navigate"
|
||
@toast="showToast"
|
||
/>
|
||
</main>
|
||
</div>
|
||
|
||
<transition name="slide-right">
|
||
<div v-if="showSettings" class="fixed inset-0 z-50">
|
||
<div class="absolute inset-0 bg-black/40" @click="showSettings = false"></div>
|
||
<div class="absolute right-0 top-0 bottom-0 w-72 bg-white dark:bg-slate-800 shadow-xl flex flex-col">
|
||
<div class="safe-area-top border-b border-slate-200 dark:border-slate-700 px-4 py-3 flex items-center justify-between">
|
||
<h2 class="font-semibold text-slate-800 dark:text-white">Einstellungen</h2>
|
||
<button @click="showSettings = false" class="p-1.5 hover:bg-slate-100 dark:hover:bg-slate-700 rounded-full text-slate-500">
|
||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
<div class="flex-1 overflow-y-auto">
|
||
<div class="px-4 py-3 border-b border-slate-100 dark:border-slate-700">
|
||
<p class="font-medium text-slate-800 dark:text-white">{{ user?.name }}</p>
|
||
<p class="text-sm text-slate-500 dark:text-slate-400">{{ user?.username }}</p>
|
||
</div>
|
||
|
||
<div class="px-4 py-3">
|
||
<p class="text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wide mb-2">Farbschema</p>
|
||
<div class="flex space-x-2">
|
||
<button
|
||
@click="setTheme('light')"
|
||
:class="[theme === 'light' ? 'ring-2 ring-primary bg-primary/5' : 'bg-slate-100 dark:bg-slate-700', 'flex-1 py-2 rounded-lg text-sm font-medium text-slate-700 dark:text-slate-300']"
|
||
>Hell</button>
|
||
<button
|
||
@click="setTheme('dark')"
|
||
:class="[theme === 'dark' ? 'ring-2 ring-primary bg-primary/5' : 'bg-slate-100 dark:bg-slate-700', 'flex-1 py-2 rounded-lg text-sm font-medium text-slate-700 dark:text-slate-300']"
|
||
>Dunkel</button>
|
||
<button
|
||
@click="setTheme('system')"
|
||
:class="[theme === 'system' ? 'ring-2 ring-primary bg-primary/5' : 'bg-slate-100 dark:bg-slate-700', 'flex-1 py-2 rounded-lg text-sm font-medium text-slate-700 dark:text-slate-300']"
|
||
>Auto</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="px-4 py-3 border-t border-slate-100 dark:border-slate-700">
|
||
<p class="text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wide mb-3">Lager</p>
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<p class="font-medium text-slate-800 dark:text-white text-sm">Simpel Modus</p>
|
||
<p class="text-xs text-slate-500 dark:text-slate-400">Weniger Optionen</p>
|
||
</div>
|
||
<button
|
||
@click="setLagerSimpleMode(!lagerSimpleMode)"
|
||
:class="[
|
||
'relative w-11 h-6 rounded-full transition-colors',
|
||
lagerSimpleMode ? 'bg-green-500' : 'bg-slate-300 dark:bg-slate-600'
|
||
]"
|
||
>
|
||
<span :class="[
|
||
'absolute top-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform',
|
||
lagerSimpleMode ? 'left-5' : 'left-0.5'
|
||
]"></span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="p-3 border-t border-slate-100 dark:border-slate-700">
|
||
<button
|
||
@click="showSettings = false; handleLogout()"
|
||
class="w-full py-2.5 px-4 text-red-600 dark:text-red-400 font-medium rounded-lg hover:bg-red-50 dark:hover:bg-red-900/20 transition flex items-center justify-center"
|
||
>
|
||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
|
||
</svg>
|
||
Abmelden
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</transition>
|
||
</template>
|
||
|
||
<transition name="slide-up">
|
||
<div v-if="toast.show" class="toast-container">
|
||
<div :class="['toast', 'toast-' + toast.type]">
|
||
{{ toast.message }}
|
||
</div>
|
||
</div>
|
||
</transition>
|
||
</div>
|
||
`
|
||
};
|
||
|
||
createApp(App).mount('#app');
|