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'; import OfflineIndicator from '/mobile/components/OfflineIndicator.js'; import SyncStatus from '/mobile/components/SyncStatus.js'; import { initDatabase, clearAllData, getStorageEstimate } from '/mobile/shared/db.js'; import offlineSettings from '/mobile/shared/offlineSettings.js'; import SyncManager from '/mobile/shared/syncManager.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, OfflineIndicator, SyncStatus }, 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 || workorderDetailOpen.value); const workorderDetailOpen = ref(false); const workorderRef = ref(null); // Offline mode state const offlineModeEnabled = ref(false); const offlineAutoSync = ref(true); const offlinePendingCount = ref(0); const offlinePendingOps = ref(0); const offlinePendingPhotos = ref(0); const offlineFailedCount = ref(0); const offlineIsSyncing = ref(false); const offlineLastSyncText = ref('Nie synchronisiert'); const offlineFreshness = ref('unknown'); const offlineSyncProgress = ref(null); const offlineStorageUsed = ref(0); const isOnline = ref(navigator.onLine); 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) {} }; // Offline mode functions const loadOfflineSettings = () => { const settings = offlineSettings.load(); offlineModeEnabled.value = settings.enabled; offlineAutoSync.value = settings.autoSync; offlineLastSyncText.value = offlineSettings.getLastSyncText(); offlineFreshness.value = offlineSettings.getFreshness(); }; const toggleOfflineMode = async () => { if (offlineModeEnabled.value) { offlineSettings.disable(); offlineModeEnabled.value = false; } else { offlineSettings.enable(); offlineModeEnabled.value = true; // Initialize database try { await initDatabase(); SyncManager.init(); } catch (error) { console.error('Failed to initialize offline mode:', error); showToast('Offline-Modus konnte nicht aktiviert werden', 'error'); offlineSettings.disable(); offlineModeEnabled.value = false; } } }; const setOfflineAutoSync = (value) => { offlineAutoSync.value = value; offlineSettings.setAutoSync(value); }; const triggerManualSync = async () => { if (!navigator.onLine) { showToast('Keine Internetverbindung', 'error'); return; } const result = await SyncManager.sync(); if (result.success) { showToast('Synchronisation abgeschlossen', 'success'); } else { showToast(result.error || 'Synchronisation fehlgeschlagen', 'error'); } }; const clearOfflineData = async () => { try { await clearAllData(); offlineSettings.clear(); offlineModeEnabled.value = false; offlinePendingCount.value = 0; showToast('Offline-Daten gelöscht', 'success'); } catch (error) { showToast('Fehler beim Löschen', 'error'); } }; const updateOfflineStatus = async () => { if (offlineModeEnabled.value) { const summary = await SyncManager.getPendingSummary(); offlinePendingCount.value = summary.total; offlinePendingOps.value = summary.operations; offlinePendingPhotos.value = summary.photos; offlineFailedCount.value = summary.failed; offlineLastSyncText.value = offlineSettings.getLastSyncText(); offlineFreshness.value = offlineSettings.getFreshness(); const storage = await getStorageEstimate(); offlineStorageUsed.value = storage.usage; } }; const handleSyncEvent = (event, data) => { switch (event) { case 'sync-start': offlineIsSyncing.value = true; offlineSyncProgress.value = null; break; case 'sync-progress': offlineSyncProgress.value = data; break; case 'sync-complete': offlineIsSyncing.value = false; offlineSyncProgress.value = null; updateOfflineStatus(); if (data.reassigned?.length > 0) { for (const wo of data.reassigned) { showToast(`Arbeitsauftrag #${wo.id} wurde neu zugewiesen`, 'warning'); } } break; case 'sync-error': offlineIsSyncing.value = false; offlineSyncProgress.value = null; break; } }; const handleOnlineStatusChange = () => { isOnline.value = navigator.onLine; }; 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 (workorderDetailOpen.value && workorderRef.value?.closeDetail) { workorderRef.value.closeDetail(); return; } if (currentSubmodule.value) { navigate(currentModule.value, null); } else if (currentModule.value) { navigate(null, null); } }; const handleWorkorderDetailOpen = (workorderId) => { workorderDetailOpen.value = true; }; const handleWorkorderDetailClose = () => { workorderDetailOpen.value = false; }; const handlePopstate = (event) => { if (workorderDetailOpen.value && workorderRef.value?.closeDetail) { workorderRef.value.closeDetail(); return; } 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); window.addEventListener('online', handleOnlineStatusChange); window.addEventListener('offline', handleOnlineStatusChange); loadLagerSettings(); loadOfflineSettings(); // Initialize offline mode if enabled if (offlineModeEnabled.value) { try { await initDatabase(); SyncManager.init(); SyncManager.subscribe(handleSyncEvent); await updateOfflineStatus(); } catch (error) { console.error('Failed to initialize offline mode:', error); } } 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); window.removeEventListener('online', handleOnlineStatusChange); window.removeEventListener('offline', handleOnlineStatusChange); if (offlineModeEnabled.value) { SyncManager.destroy(); } }); 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, workorderRef, handleWorkorderDetailOpen, handleWorkorderDetailClose, // Offline mode offlineModeEnabled, offlineAutoSync, offlinePendingCount, offlinePendingOps, offlinePendingPhotos, offlineFailedCount, offlineIsSyncing, offlineLastSyncText, offlineFreshness, offlineSyncProgress, offlineStorageUsed, isOnline, toggleOfflineMode, setOfflineAutoSync, triggerManualSync, clearOfflineData, }; }, template: `
Lädt...
Logo

App installieren

Für die beste Erfahrung installiere die App auf deinem Gerät.

So installierst du die App:

  1. 1 Tippe auf das Teilen-Symbol
  2. 2 Scrolle und wähle "Zum Home-Bildschirm"
  3. 3 Tippe auf "Hinzufügen"

So installierst du die App:

  1. 1 Tippe auf das Menü (⋮) oben rechts
  2. 2 Wähle "App installieren" oder "Zum Startbildschirm hinzufügen"
  3. 3 Bestätige mit "Installieren"

Hinweis: Diese App ist für mobile Geräte optimiert. Bitte öffne diese Seite auf deinem Smartphone und installiere die App.

powered by XINON

{{ toast.message }}
` }; createApp(App).mount('#app');