267 lines
14 KiB
JavaScript
267 lines
14 KiB
JavaScript
/**
|
|
* StocktakeList Component
|
|
*
|
|
* Displays a list of active stocktakes that the user can participate in.
|
|
* Includes settings menu with theme toggle and logout.
|
|
*/
|
|
|
|
import { api } from '/mobile/shared/auth.js';
|
|
|
|
export default {
|
|
name: 'StocktakeList',
|
|
emits: ['select', 'logout', 'set-theme'],
|
|
props: {
|
|
user: {
|
|
type: Object,
|
|
default: null
|
|
},
|
|
theme: {
|
|
type: String,
|
|
default: 'system'
|
|
}
|
|
},
|
|
|
|
setup(props, { emit }) {
|
|
const { ref, onMounted } = Vue;
|
|
|
|
// State
|
|
const stocktakes = ref([]);
|
|
const isLoading = ref(true);
|
|
const error = ref('');
|
|
const isSettingsOpen = ref(false);
|
|
|
|
// Fetch stocktakes
|
|
const fetchStocktakes = async () => {
|
|
isLoading.value = true;
|
|
error.value = '';
|
|
|
|
try {
|
|
const result = await api.get('WarehouseStocktake/getActiveStocktakes');
|
|
|
|
if (result.success) {
|
|
stocktakes.value = result.stocktakes;
|
|
} else {
|
|
error.value = result.error || 'Fehler beim Laden';
|
|
}
|
|
} catch (e) {
|
|
error.value = 'Netzwerkfehler';
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
};
|
|
|
|
const selectStocktake = (stocktake) => {
|
|
emit('select', stocktake);
|
|
};
|
|
|
|
const handleLogout = () => {
|
|
isSettingsOpen.value = false;
|
|
emit('logout');
|
|
};
|
|
|
|
const setTheme = (newTheme) => {
|
|
emit('set-theme', newTheme);
|
|
};
|
|
|
|
onMounted(() => {
|
|
fetchStocktakes();
|
|
});
|
|
|
|
return {
|
|
stocktakes,
|
|
isLoading,
|
|
error,
|
|
isSettingsOpen,
|
|
fetchStocktakes,
|
|
selectStocktake,
|
|
handleLogout,
|
|
setTheme
|
|
};
|
|
},
|
|
|
|
template: `
|
|
<div class="flex flex-col h-full bg-slate-100 dark:bg-slate-900">
|
|
<!-- Overlay for settings -->
|
|
<transition name="fade">
|
|
<div v-if="isSettingsOpen" @click="isSettingsOpen = false" class="overlay"></div>
|
|
</transition>
|
|
|
|
<!-- Header -->
|
|
<header class="bg-white dark:bg-slate-800 shadow-sm p-4 flex-shrink-0 z-10">
|
|
<div class="flex items-center justify-between">
|
|
<!-- Refresh Button -->
|
|
<button
|
|
@click="fetchStocktakes"
|
|
class="p-2 rounded-full text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700 active:bg-slate-200 dark:active:bg-slate-600 transition"
|
|
>
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" :class="{ 'animate-spin': isLoading }" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
</svg>
|
|
</button>
|
|
|
|
<!-- Logo -->
|
|
<div>
|
|
<img src="/assets/images/xinon-full-transparent.png" class="h-8 w-auto dark:hidden" alt="Logo">
|
|
<img src="/assets/images/xinon-full-transparent-white.png" class="h-8 w-auto hidden dark:block" alt="Logo">
|
|
</div>
|
|
|
|
<!-- Settings Button -->
|
|
<button
|
|
@click="isSettingsOpen = true"
|
|
class="p-2 rounded-full text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700 active:bg-slate-200 dark:active:bg-slate-600 transition"
|
|
>
|
|
<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="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>
|
|
</div>
|
|
|
|
<!-- Title -->
|
|
<h1 class="text-xl font-bold text-slate-800 dark:text-white mt-4">
|
|
Aktive Inventuren
|
|
</h1>
|
|
<p v-if="user" class="text-sm text-slate-500 dark:text-slate-400">
|
|
Angemeldet als {{ user.name }}
|
|
</p>
|
|
</header>
|
|
|
|
<!-- Content -->
|
|
<main class="flex-grow overflow-y-auto p-4">
|
|
<!-- Loading State -->
|
|
<div v-if="isLoading" class="space-y-3">
|
|
<div v-for="i in 3" :key="i" class="bg-white dark:bg-slate-800 p-4 rounded-lg shadow animate-pulse">
|
|
<div class="h-5 bg-slate-200 dark:bg-slate-700 rounded w-3/4 mb-2"></div>
|
|
<div class="h-4 bg-slate-200 dark:bg-slate-700 rounded w-1/2 mb-3"></div>
|
|
<div class="h-3 bg-slate-200 dark:bg-slate-700 rounded w-1/3"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Error State -->
|
|
<div v-else-if="error" class="text-center py-8">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto text-red-400 mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
<p class="text-slate-500 dark:text-slate-400 mb-4">{{ error }}</p>
|
|
<button @click="fetchStocktakes" class="px-4 py-2 bg-primary text-white rounded-lg font-medium">
|
|
Erneut versuchen
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Empty State -->
|
|
<div v-else-if="stocktakes.length === 0" class="text-center py-8">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto text-slate-300 dark:text-slate-600 mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
|
</svg>
|
|
<p class="text-slate-500 dark:text-slate-400">Keine aktiven Inventuren</p>
|
|
</div>
|
|
|
|
<!-- Stocktake List -->
|
|
<div v-else class="space-y-3">
|
|
<div
|
|
v-for="stocktake in stocktakes"
|
|
:key="stocktake.id"
|
|
@click="selectStocktake(stocktake)"
|
|
class="bg-white dark:bg-slate-800 p-4 rounded-lg shadow-sm cursor-pointer hover:shadow-md active:scale-[0.98] transition"
|
|
>
|
|
<div class="flex justify-between items-start">
|
|
<div>
|
|
<h3 class="font-bold text-slate-800 dark:text-white">
|
|
{{ stocktake.title || 'Inventur #' + stocktake.stocktakeNumber }}
|
|
</h3>
|
|
<p class="text-sm text-slate-500 dark:text-slate-400 mt-1">
|
|
{{ stocktake.locationName }}
|
|
</p>
|
|
</div>
|
|
<div class="text-right">
|
|
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400">
|
|
Aktiv
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="flex justify-between items-center mt-3 pt-3 border-t border-slate-100 dark:border-slate-700">
|
|
<div class="text-sm text-slate-500 dark:text-slate-400">
|
|
<span class="font-medium text-slate-700 dark:text-slate-300">{{ stocktake.totalScannedItems || 0 }}</span>
|
|
Artikel gescannt
|
|
</div>
|
|
<div class="text-xs text-slate-400 dark:text-slate-500">
|
|
{{ stocktake.startedAt || 'Nicht gestartet' }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<!-- Settings Panel -->
|
|
<transition name="slide">
|
|
<div v-if="isSettingsOpen" class="fixed inset-y-0 right-0 w-80 max-w-full bg-white dark:bg-slate-800 shadow-xl z-20 flex flex-col">
|
|
<div class="p-4 border-b border-slate-200 dark:border-slate-700 flex items-center justify-between">
|
|
<h2 class="text-lg font-bold text-slate-800 dark:text-white">Einstellungen</h2>
|
|
<button @click="isSettingsOpen = false" class="p-2 rounded-full hover:bg-slate-100 dark:hover:bg-slate-700">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-slate-500" 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-grow overflow-y-auto p-4 space-y-6">
|
|
<!-- User Info -->
|
|
<div v-if="user" class="pb-4 border-b border-slate-200 dark:border-slate-700">
|
|
<p class="text-sm text-slate-500 dark:text-slate-400">Angemeldet als</p>
|
|
<p class="font-medium text-slate-800 dark:text-white">{{ user.name }}</p>
|
|
</div>
|
|
|
|
<!-- Theme Settings -->
|
|
<div>
|
|
<h3 class="text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3">Farbschema</h3>
|
|
<div class="grid grid-cols-3 gap-2">
|
|
<button
|
|
@click="setTheme('light')"
|
|
:class="{'ring-2 ring-primary': theme === 'light'}"
|
|
class="p-2 text-sm font-medium rounded-lg border border-slate-300 dark:border-slate-600 hover:bg-slate-50 dark:hover:bg-slate-700 transition text-slate-700 dark:text-slate-300"
|
|
>
|
|
Hell
|
|
</button>
|
|
<button
|
|
@click="setTheme('dark')"
|
|
:class="{'ring-2 ring-primary': theme === 'dark'}"
|
|
class="p-2 text-sm font-medium rounded-lg border border-slate-300 dark:border-slate-600 hover:bg-slate-50 dark:hover:bg-slate-700 transition text-slate-700 dark:text-slate-300"
|
|
>
|
|
Dunkel
|
|
</button>
|
|
<button
|
|
@click="setTheme('system')"
|
|
:class="{'ring-2 ring-primary': theme === 'system'}"
|
|
class="p-2 text-sm font-medium rounded-lg border border-slate-300 dark:border-slate-600 hover:bg-slate-50 dark:hover:bg-slate-700 transition text-slate-700 dark:text-slate-300"
|
|
>
|
|
System
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div class="p-4 border-t border-slate-200 dark:border-slate-700 space-y-4">
|
|
<button
|
|
@click="handleLogout"
|
|
class="w-full flex items-center justify-center px-4 py-3 text-red-600 dark:text-red-400 font-medium rounded-lg hover:bg-red-50 dark:hover:bg-red-900/20 transition"
|
|
>
|
|
<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 class="text-center">
|
|
<img src="/assets/images/xinon-sm.png" class="h-8 mx-auto mb-2" alt="XINON">
|
|
<p class="text-xs text-slate-400 dark:text-slate-500">
|
|
powered by XINON GmbH
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</transition>
|
|
</div>
|
|
`
|
|
};
|