Xinon mobile/improve
This commit is contained in:
@@ -1,20 +1,8 @@
|
||||
/**
|
||||
* MovementForm Component (WarehouseMovement)
|
||||
*
|
||||
* The main interface for stock movements (IN/OUT/ADJUSTMENT).
|
||||
* API: /MobileApp/Lager/Movement/{action}
|
||||
*/
|
||||
import { createModuleApi, debounce } from '/mobile/shared/api.js';
|
||||
|
||||
const movementApi = createModuleApi('Lager/Movement');
|
||||
|
||||
const movementApi = {
|
||||
get: (endpoint) => fetch(`/MobileApp/Lager/Movement/${endpoint}`).then(r => r.json()),
|
||||
post: (endpoint, data) => fetch(`/MobileApp/Lager/Movement/${endpoint}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
}).then(r => r.json())
|
||||
};
|
||||
|
||||
// Custom BottomSheet Select Component
|
||||
const BottomSheetSelect = {
|
||||
name: 'BottomSheetSelect',
|
||||
emits: ['update:modelValue'],
|
||||
@@ -212,6 +200,14 @@ export default {
|
||||
const recentMovements = ref([]);
|
||||
const isLoadingHistory = ref(false);
|
||||
|
||||
// ==================== ORDER RECEIVING ====================
|
||||
const pendingOrders = ref([]);
|
||||
const isLoadingOrders = ref(false);
|
||||
const selectedOrder = ref(null);
|
||||
const orderPositions = ref([]);
|
||||
const deliveryNotePhoto = ref(null);
|
||||
const isSubmittingOrder = ref(false);
|
||||
|
||||
// ==================== KEYPAD ====================
|
||||
const showKeypad = ref(false);
|
||||
const showNote = ref(false);
|
||||
@@ -569,6 +565,9 @@ export default {
|
||||
const result = await movementApi.post('submitMovement', payload);
|
||||
|
||||
if (result.success) {
|
||||
// Haptic feedback on success
|
||||
navigator.vibrate?.([100]);
|
||||
|
||||
// Store for undo
|
||||
lastMovement.value = result.movement;
|
||||
showUndo.value = true;
|
||||
@@ -667,6 +666,8 @@ export default {
|
||||
isLoading.value = false;
|
||||
|
||||
if (errorCount === 0) {
|
||||
// Haptic feedback on success
|
||||
navigator.vibrate?.([100, 50, 100]);
|
||||
emit('toast', `${successCount} Bewegungen erfolgreich`, 'success');
|
||||
clearCart();
|
||||
} else {
|
||||
@@ -709,6 +710,8 @@ export default {
|
||||
const result = await movementApi.post('submitMovement', payload);
|
||||
|
||||
if (result.success) {
|
||||
// Haptic feedback on success
|
||||
navigator.vibrate?.([100]);
|
||||
emit('toast', result.message, 'success');
|
||||
// Reset form
|
||||
scannedArticle.value = null;
|
||||
@@ -727,8 +730,7 @@ export default {
|
||||
}
|
||||
};
|
||||
|
||||
// Search
|
||||
const searchArticles = async () => {
|
||||
const doSearch = async () => {
|
||||
if (searchQuery.value.length < 2) {
|
||||
searchResults.value = [];
|
||||
return;
|
||||
@@ -742,6 +744,8 @@ export default {
|
||||
}
|
||||
};
|
||||
|
||||
const searchArticles = debounce(doSearch, 300);
|
||||
|
||||
const selectSearchResult = async (article) => {
|
||||
await stopScanner();
|
||||
scannedArticle.value = article;
|
||||
@@ -767,6 +771,97 @@ export default {
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== ORDER RECEIVING FUNCTIONS ====================
|
||||
const loadPendingOrders = async () => {
|
||||
isLoadingOrders.value = true;
|
||||
try {
|
||||
const result = await movementApi.get('getPendingOrders');
|
||||
if (result.success) {
|
||||
pendingOrders.value = result.orders;
|
||||
}
|
||||
} catch (e) {
|
||||
emit('toast', 'Fehler beim Laden der Bestellungen', 'error');
|
||||
} finally {
|
||||
isLoadingOrders.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const selectOrderForReceiving = async (order) => {
|
||||
isLoadingOrders.value = true;
|
||||
try {
|
||||
const result = await movementApi.get(`getOrderForReceiving?orderId=${order.id}`);
|
||||
if (result.success) {
|
||||
selectedOrder.value = result.order;
|
||||
orderPositions.value = result.positions;
|
||||
deliveryNotePhoto.value = null;
|
||||
}
|
||||
} catch (e) {
|
||||
emit('toast', 'Fehler beim Laden der Bestellung', 'error');
|
||||
} finally {
|
||||
isLoadingOrders.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const cancelOrderReceiving = () => {
|
||||
selectedOrder.value = null;
|
||||
orderPositions.value = [];
|
||||
deliveryNotePhoto.value = null;
|
||||
};
|
||||
|
||||
const submitOrderReceiving = async () => {
|
||||
if (!selectedOrder.value || !selectedLocation.value) return;
|
||||
|
||||
// Collect positions with quantity > 0
|
||||
const positionsToSubmit = orderPositions.value
|
||||
.filter(p => p.receivingQty > 0)
|
||||
.map(p => ({
|
||||
articleId: p.articleId,
|
||||
quantity: p.receivingQty
|
||||
}));
|
||||
|
||||
if (positionsToSubmit.length === 0) {
|
||||
emit('toast', 'Bitte mindestens eine Menge eingeben', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
isSubmittingOrder.value = true;
|
||||
try {
|
||||
const result = await movementApi.post('submitOrderReceiving', {
|
||||
orderId: selectedOrder.value.id,
|
||||
locationId: selectedLocation.value,
|
||||
positions: positionsToSubmit,
|
||||
deliveryNoteFileId: deliveryNotePhoto.value,
|
||||
note: null
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
navigator.vibrate?.([100, 50, 100]);
|
||||
emit('toast', result.message, 'success');
|
||||
// Reset and reload orders
|
||||
cancelOrderReceiving();
|
||||
await loadPendingOrders();
|
||||
} else {
|
||||
emit('toast', result.message || 'Fehler beim Speichern', 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
emit('toast', 'Netzwerkfehler', 'error');
|
||||
} finally {
|
||||
isSubmittingOrder.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const setAllReceivingQty = () => {
|
||||
orderPositions.value.forEach(p => {
|
||||
p.receivingQty = p.remainingQty;
|
||||
});
|
||||
};
|
||||
|
||||
const clearAllReceivingQty = () => {
|
||||
orderPositions.value.forEach(p => {
|
||||
p.receivingQty = 0;
|
||||
});
|
||||
};
|
||||
|
||||
// Keypad
|
||||
const appendDigit = (digit) => {
|
||||
if (digit === '.' && quantity.value.includes('.')) return;
|
||||
@@ -794,6 +889,9 @@ export default {
|
||||
} else if (tab === 'history') {
|
||||
await stopScanner();
|
||||
await loadHistory();
|
||||
} else if (tab === 'orders') {
|
||||
await stopScanner();
|
||||
await loadPendingOrders();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -849,6 +947,11 @@ export default {
|
||||
searchQuery, searchResults, isSearching,
|
||||
// History
|
||||
recentMovements, isLoadingHistory,
|
||||
// Order Receiving
|
||||
pendingOrders, isLoadingOrders, selectedOrder, orderPositions,
|
||||
deliveryNotePhoto, isSubmittingOrder,
|
||||
loadPendingOrders, selectOrderForReceiving, cancelOrderReceiving,
|
||||
submitOrderReceiving, setAllReceivingQty, clearAllReceivingQty,
|
||||
// UI
|
||||
showKeypad, showNote, canSubmit, typeColor,
|
||||
// Functions
|
||||
@@ -923,29 +1026,30 @@ export default {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Mode Toggles & Quick Actions Bar (hidden in simple mode) -->
|
||||
<!-- Mode Toggles (hidden in simple mode) -->
|
||||
<div v-if="!simpleMode" class="flex items-center gap-2">
|
||||
<!-- Turbo Mode Toggle -->
|
||||
<!-- Turbo Mode Toggle - More Prominent -->
|
||||
<button
|
||||
@click="turboMode = !turboMode"
|
||||
:class="[
|
||||
'flex items-center gap-1.5 px-3 py-2 rounded-lg text-xs font-medium transition-all',
|
||||
'flex items-center gap-2 px-4 py-2.5 rounded-xl font-medium transition-all flex-1',
|
||||
turboMode
|
||||
? 'bg-orange-500 text-white'
|
||||
: 'bg-white dark:bg-slate-800 text-slate-500 dark:text-slate-400 border border-slate-200 dark:border-slate-700'
|
||||
? 'bg-gradient-to-r from-orange-500 to-orange-600 text-white shadow-lg shadow-orange-500/30'
|
||||
: 'bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-400 border-2 border-dashed border-orange-300 dark:border-orange-800 hover:border-orange-400'
|
||||
]"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<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="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
Turbo
|
||||
<span class="text-sm">Turbo-Modus</span>
|
||||
<span v-if="!turboMode" class="text-xs opacity-60">(1-Klick)</span>
|
||||
</button>
|
||||
|
||||
<!-- Batch Mode Toggle -->
|
||||
<button
|
||||
@click="batchMode = !batchMode"
|
||||
:class="[
|
||||
'flex items-center gap-1.5 px-3 py-2 rounded-lg text-xs font-medium transition-all',
|
||||
'flex items-center gap-1.5 px-3 py-2.5 rounded-xl text-sm font-medium transition-all',
|
||||
batchMode
|
||||
? 'bg-purple-500 text-white'
|
||||
: 'bg-white dark:bg-slate-800 text-slate-500 dark:text-slate-400 border border-slate-200 dark:border-slate-700'
|
||||
@@ -957,18 +1061,6 @@ export default {
|
||||
Sammel
|
||||
<span v-if="cartTotal > 0" class="bg-white/30 px-1.5 rounded-full">{{ cartTotal }}</span>
|
||||
</button>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="flex-1"></div>
|
||||
<button
|
||||
@click="quickAction('OUT', 'Verbrauch')"
|
||||
class="flex items-center gap-1 px-3 py-2 rounded-lg text-xs font-medium bg-red-100 dark:bg-red-900/30 text-red-600 dark:text-red-400 hover:bg-red-200 dark:hover:bg-red-900/50 transition"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H4" />
|
||||
</svg>
|
||||
Schnell
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -976,6 +1068,10 @@ export default {
|
||||
<div class="flex bg-slate-100 dark:bg-slate-900 border-b border-slate-200 dark:border-slate-700 px-3 py-2">
|
||||
<button @click="switchTab('scan')" :class="[currentTab === 'scan' ? 'bg-white dark:bg-slate-800 shadow-sm' : 'text-slate-500 dark:text-slate-400']" class="flex-1 py-2 text-sm font-medium rounded-lg transition">Scannen</button>
|
||||
<button @click="switchTab('search')" :class="[currentTab === 'search' ? 'bg-white dark:bg-slate-800 shadow-sm' : 'text-slate-500 dark:text-slate-400']" class="flex-1 py-2 text-sm font-medium rounded-lg transition mx-1">Suche</button>
|
||||
<button v-if="!simpleMode" @click="switchTab('orders')" :class="[currentTab === 'orders' ? 'bg-white dark:bg-slate-800 shadow-sm' : 'text-slate-500 dark:text-slate-400', pendingOrders.length > 0 ? 'relative' : '']" class="flex-1 py-2 text-sm font-medium rounded-lg transition mx-1">
|
||||
Bestellung
|
||||
<span v-if="pendingOrders.length > 0 && currentTab !== 'orders'" class="absolute -top-1 -right-1 bg-blue-500 text-white text-xs w-5 h-5 rounded-full flex items-center justify-center">{{ pendingOrders.length }}</span>
|
||||
</button>
|
||||
<button v-if="!simpleMode" @click="switchTab('history')" :class="[currentTab === 'history' ? 'bg-white dark:bg-slate-800 shadow-sm' : 'text-slate-500 dark:text-slate-400']" class="flex-1 py-2 text-sm font-medium rounded-lg transition">Verlauf</button>
|
||||
</div>
|
||||
|
||||
@@ -1016,17 +1112,30 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quantity (Compact) -->
|
||||
<div @click="showKeypad = true" :class="[
|
||||
'flex items-center justify-between p-3 rounded-xl cursor-pointer transition active:scale-[0.98]',
|
||||
selectedType === 'IN' ? 'bg-green-500 text-white' : '',
|
||||
selectedType === 'OUT' ? 'bg-red-500 text-white' : '',
|
||||
selectedType === 'ADJUSTMENT' ? 'bg-yellow-500 text-white' : ''
|
||||
]">
|
||||
<span class="font-medium">Menge</span>
|
||||
<span class="text-2xl font-bold">
|
||||
{{ selectedType === 'OUT' ? '-' : selectedType === 'IN' ? '+' : '' }}{{ quantity }}
|
||||
</span>
|
||||
<!-- Quantity with Quick Buttons -->
|
||||
<div class="bg-white dark:bg-slate-800 p-3 rounded-xl shadow-sm">
|
||||
<!-- Quick preset buttons -->
|
||||
<div class="flex gap-2 mb-2">
|
||||
<button @click="quantity = '1'" :class="['flex-1 py-2 rounded-lg font-bold text-sm transition', quantity === '1' ? 'bg-primary text-white' : 'bg-slate-100 dark:bg-slate-700 text-slate-700 dark:text-slate-300']">1</button>
|
||||
<button @click="quantity = '5'" :class="['flex-1 py-2 rounded-lg font-bold text-sm transition', quantity === '5' ? 'bg-primary text-white' : 'bg-slate-100 dark:bg-slate-700 text-slate-700 dark:text-slate-300']">5</button>
|
||||
<button @click="quantity = '10'" :class="['flex-1 py-2 rounded-lg font-bold text-sm transition', quantity === '10' ? 'bg-primary text-white' : 'bg-slate-100 dark:bg-slate-700 text-slate-700 dark:text-slate-300']">10</button>
|
||||
<button @click="quantity = '20'" :class="['flex-1 py-2 rounded-lg font-bold text-sm transition', quantity === '20' ? 'bg-primary text-white' : 'bg-slate-100 dark:bg-slate-700 text-slate-700 dark:text-slate-300']">20</button>
|
||||
</div>
|
||||
<!-- Quantity with +/- and colored display -->
|
||||
<div class="flex items-center gap-2">
|
||||
<button @click="quantity = String(Math.max(1, parseFloat(quantity) - 1))" class="w-12 h-12 bg-slate-100 dark:bg-slate-700 rounded-lg text-xl font-bold text-slate-700 dark:text-slate-300 active:bg-slate-200 dark:active:bg-slate-600">-</button>
|
||||
<div @click="showKeypad = true" :class="[
|
||||
'flex-1 py-3 rounded-xl cursor-pointer transition active:scale-[0.98] text-center',
|
||||
selectedType === 'IN' ? 'bg-green-500 text-white' : '',
|
||||
selectedType === 'OUT' ? 'bg-red-500 text-white' : '',
|
||||
selectedType === 'ADJUSTMENT' ? 'bg-yellow-500 text-white' : ''
|
||||
]">
|
||||
<span class="text-2xl font-bold">
|
||||
{{ selectedType === 'OUT' ? '-' : selectedType === 'IN' ? '+' : '' }}{{ quantity }}
|
||||
</span>
|
||||
</div>
|
||||
<button @click="quantity = String(parseFloat(quantity) + 1)" class="w-12 h-12 bg-slate-100 dark:bg-slate-700 rounded-lg text-xl font-bold text-slate-700 dark:text-slate-300 active:bg-slate-200 dark:active:bg-slate-600">+</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Reason + Note Button (Combined Row) -->
|
||||
@@ -1065,19 +1174,13 @@ export default {
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Buttons Row -->
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
@click="cancelScan"
|
||||
class="flex-1 py-3 font-medium rounded-xl bg-slate-200 dark:bg-slate-700 text-slate-600 dark:text-slate-300 transition active:scale-[0.98]"
|
||||
>
|
||||
Abbrechen
|
||||
</button>
|
||||
<!-- Buttons -->
|
||||
<div class="space-y-2">
|
||||
<button
|
||||
@click="submitMovement"
|
||||
:disabled="!canSubmit"
|
||||
:class="[
|
||||
'flex-[2] py-3 font-bold rounded-xl text-white transition active:scale-[0.98] disabled:opacity-50 disabled:active:scale-100',
|
||||
'w-full py-5 text-lg font-bold rounded-xl text-white transition active:scale-[0.98] disabled:opacity-50 disabled:active:scale-100',
|
||||
selectedType === 'IN' ? 'bg-green-600' : '',
|
||||
selectedType === 'OUT' ? 'bg-red-600' : '',
|
||||
selectedType === 'ADJUSTMENT' ? 'bg-yellow-600' : ''
|
||||
@@ -1085,6 +1188,12 @@ export default {
|
||||
>
|
||||
{{ isLoading ? 'Speichert...' : (selectedType === 'IN' ? 'Einbuchen' : selectedType === 'OUT' ? 'Ausbuchen' : 'Korrigieren') }}
|
||||
</button>
|
||||
<button
|
||||
@click="cancelScan"
|
||||
class="w-full py-2 text-sm text-slate-500 dark:text-slate-400 hover:text-slate-700 dark:hover:text-slate-300"
|
||||
>
|
||||
Abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1163,6 +1272,149 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ORDERS TAB -->
|
||||
<div v-else-if="currentTab === 'orders'" class="p-4">
|
||||
<!-- Loading -->
|
||||
<div v-if="isLoadingOrders" class="space-y-3">
|
||||
<div v-for="i in 3" :key="i" class="bg-white dark:bg-slate-800 p-4 rounded-xl shadow-sm animate-pulse">
|
||||
<div class="h-5 bg-slate-200 dark:bg-slate-700 rounded w-1/2 mb-3"></div>
|
||||
<div class="h-4 bg-slate-200 dark:bg-slate-700 rounded w-3/4"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Order List (when no order selected) -->
|
||||
<template v-else-if="!selectedOrder">
|
||||
<div v-if="pendingOrders.length === 0" class="text-center py-12">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 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="1.5" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
|
||||
</svg>
|
||||
<p class="text-slate-500 dark:text-slate-400 text-lg font-medium">Keine offenen Bestellungen</p>
|
||||
<p class="text-slate-400 dark:text-slate-500 text-sm mt-1">Alle Lieferungen wurden empfangen</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="space-y-3">
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400 mb-2">{{ pendingOrders.length }} Bestellung(en) warten auf Wareneingang:</p>
|
||||
<div
|
||||
v-for="order in pendingOrders"
|
||||
:key="order.id"
|
||||
@click="selectOrderForReceiving(order)"
|
||||
class="bg-white dark:bg-slate-800 p-4 rounded-xl shadow-sm cursor-pointer hover:shadow-md active:scale-[0.98] transition"
|
||||
>
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span class="font-bold text-slate-800 dark:text-white">{{ order.orderNumber }}</span>
|
||||
<span :class="[
|
||||
'px-2 py-0.5 text-xs font-medium rounded',
|
||||
order.status === 'sent' ? 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400' : 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400'
|
||||
]">
|
||||
{{ order.statusLabel }}
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-slate-600 dark:text-slate-300">{{ order.distributorName }}</p>
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400 mt-1">
|
||||
{{ order.positionCount }} Position(en) · {{ order.totalItems }} Artikel gesamt
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<p class="text-xs text-slate-400">{{ order.create }}</p>
|
||||
<p v-if="order.daysSinceSent > 0" :class="['text-xs mt-1', order.daysSinceSent > 7 ? 'text-red-500 font-medium' : 'text-slate-400']">
|
||||
{{ order.daysSinceSent }} Tag(e)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 flex items-center gap-2 text-primary">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium">Wareneingang erfassen</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Order Receiving Form (when order selected) -->
|
||||
<template v-else>
|
||||
<!-- Header -->
|
||||
<div class="bg-white dark:bg-slate-800 p-4 rounded-xl shadow-sm mb-4">
|
||||
<div class="flex items-start justify-between mb-2">
|
||||
<div>
|
||||
<h3 class="font-bold text-lg text-slate-800 dark:text-white">{{ selectedOrder.orderNumber }}</h3>
|
||||
<p class="text-slate-600 dark:text-slate-300">{{ selectedOrder.distributorName }}</p>
|
||||
</div>
|
||||
<button @click="cancelOrderReceiving" class="p-2 hover:bg-slate-100 dark:hover:bg-slate-700 rounded-lg transition">
|
||||
<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>
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400">Bestellt: {{ selectedOrder.create }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="flex gap-2 mb-4">
|
||||
<button @click="setAllReceivingQty" class="flex-1 py-2 px-3 bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-400 rounded-lg text-sm font-medium transition active:scale-[0.98]">
|
||||
Alle übernehmen
|
||||
</button>
|
||||
<button @click="clearAllReceivingQty" class="flex-1 py-2 px-3 bg-slate-100 dark:bg-slate-700 text-slate-600 dark:text-slate-300 rounded-lg text-sm font-medium transition active:scale-[0.98]">
|
||||
Alle löschen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Positions -->
|
||||
<div class="space-y-3 mb-4">
|
||||
<div
|
||||
v-for="(pos, index) in orderPositions"
|
||||
:key="pos.articleId"
|
||||
class="bg-white dark:bg-slate-800 p-4 rounded-xl shadow-sm"
|
||||
>
|
||||
<div class="flex items-start justify-between mb-3">
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="font-medium text-slate-800 dark:text-white truncate">{{ pos.articleTitle }}</p>
|
||||
<p class="text-xs text-slate-500 dark:text-slate-400">{{ pos.articleNumber }}</p>
|
||||
</div>
|
||||
<div class="text-right text-sm text-slate-500 dark:text-slate-400 ml-2">
|
||||
<p>Bestellt: {{ pos.orderedQty }}</p>
|
||||
<p v-if="pos.deliveredQty > 0" class="text-green-600 dark:text-green-400">Erhalten: {{ pos.deliveredQty }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-sm text-slate-500 dark:text-slate-400 w-20">Empfangen:</span>
|
||||
<div class="flex items-center gap-2 flex-1">
|
||||
<button
|
||||
@click="orderPositions[index].receivingQty = Math.max(0, pos.receivingQty - 1)"
|
||||
class="w-10 h-10 bg-slate-100 dark:bg-slate-700 rounded-lg text-lg font-bold text-slate-600 dark:text-slate-300 active:bg-slate-200 dark:active:bg-slate-600"
|
||||
>-</button>
|
||||
<input
|
||||
type="number"
|
||||
v-model.number="orderPositions[index].receivingQty"
|
||||
min="0"
|
||||
:max="pos.remainingQty"
|
||||
class="flex-1 text-center py-2 px-3 bg-slate-50 dark:bg-slate-700 border border-slate-200 dark:border-slate-600 rounded-lg text-lg font-bold text-slate-800 dark:text-white"
|
||||
/>
|
||||
<button
|
||||
@click="orderPositions[index].receivingQty = Math.min(pos.remainingQty, pos.receivingQty + 1)"
|
||||
class="w-10 h-10 bg-slate-100 dark:bg-slate-700 rounded-lg text-lg font-bold text-slate-600 dark:text-slate-300 active:bg-slate-200 dark:active:bg-slate-600"
|
||||
>+</button>
|
||||
</div>
|
||||
<span class="text-xs text-slate-400 w-16 text-right">/ {{ pos.remainingQty }} {{ pos.unit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Submit -->
|
||||
<div class="sticky bottom-0 bg-slate-50 dark:bg-slate-900 -mx-4 -mb-4 p-4 border-t border-slate-200 dark:border-slate-700">
|
||||
<button
|
||||
@click="submitOrderReceiving"
|
||||
:disabled="isSubmittingOrder"
|
||||
class="w-full py-4 bg-green-600 text-white text-lg font-bold rounded-xl transition active:scale-[0.98] disabled:opacity-50 disabled:active:scale-100"
|
||||
>
|
||||
{{ isSubmittingOrder ? 'Wird gespeichert...' : 'Wareneingang buchen' }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Keypad -->
|
||||
|
||||
Reference in New Issue
Block a user