183 lines
5.9 KiB
JavaScript
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');
|