Files
thetool/public/mobile/warehouse-stocktake/app.js
2026-01-13 12:44:45 +01:00

183 lines
5.9 KiB
JavaScript

/**
* Warehouse Stocktake PWA - Main Vue Application
*
* This is the entry point for the Warehouse Stocktake PWA.
* It manages authentication state and routes between views.
*/
// Import shared modules
import { api, authState, checkAuth, login, logout } from '/mobile/shared/auth.js';
// Import components
import LoginScreen from './components/LoginScreen.js';
import StocktakeList from './components/StocktakeList.js';
import Scanner from './components/Scanner.js';
const { createApp, ref, computed, onMounted, watch, nextTick } = Vue;
const App = {
components: {
LoginScreen,
StocktakeList,
Scanner
},
setup() {
// ==================== STATE ====================
const currentView = ref('loading'); // 'loading', 'login', 'list', 'scanner'
const user = ref(null);
const selectedStocktake = ref(null);
const toast = ref({ show: false, message: '', type: 'success' });
const theme = ref('system');
// ==================== THEME ====================
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();
};
// ==================== AUTH ====================
const handleLogin = async (credentials) => {
const result = await login(credentials);
if (result.success) {
user.value = result.user;
currentView.value = 'list';
showToast('Erfolgreich angemeldet', 'success');
}
return result;
};
const handleLogout = async () => {
await logout();
user.value = null;
selectedStocktake.value = null;
currentView.value = 'login';
showToast('Abgemeldet', 'success');
};
// ==================== NAVIGATION ====================
const openScanner = (stocktake) => {
selectedStocktake.value = stocktake;
currentView.value = 'scanner';
};
const closeScanner = () => {
selectedStocktake.value = null;
currentView.value = 'list';
};
// ==================== TOAST ====================
const showToast = (message, type = 'success') => {
toast.value = { show: true, message, type };
setTimeout(() => {
toast.value.show = false;
}, 3000);
};
// ==================== LIFECYCLE ====================
onMounted(async () => {
// Initialize theme
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
theme.value = savedTheme;
}
applyTheme();
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', applyTheme);
// Check authentication
const result = await checkAuth();
if (result.authenticated) {
user.value = result.user;
currentView.value = 'list';
} else {
currentView.value = 'login';
}
});
return {
// State
currentView,
user,
selectedStocktake,
toast,
theme,
// Methods
handleLogin,
handleLogout,
openScanner,
closeScanner,
showToast,
setTheme,
};
},
template: `
<div class="relative h-full w-full bg-slate-100 dark:bg-slate-900 transition-colors duration-300">
<!-- Loading State -->
<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>
<!-- Login Screen -->
<LoginScreen
v-else-if="currentView === 'login'"
@login="handleLogin"
:theme="theme"
@set-theme="setTheme"
/>
<!-- Stocktake List -->
<StocktakeList
v-else-if="currentView === 'list'"
:user="user"
:theme="theme"
@select="openScanner"
@logout="handleLogout"
@set-theme="setTheme"
/>
<!-- Scanner View -->
<Scanner
v-else-if="currentView === 'scanner'"
:stocktake="selectedStocktake"
:user="user"
@close="closeScanner"
@toast="showToast"
/>
<!-- Toast Notifications -->
<transition name="slide-up">
<div v-if="toast.show" class="toast-container">
<div :class="['toast', 'toast-' + toast.type]">
{{ toast.message }}
</div>
</div>
</transition>
</div>
`
};
// Mount the app
createApp(App).mount('#app');