228 lines
9.6 KiB
JavaScript
228 lines
9.6 KiB
JavaScript
/**
|
|
* ShippingNoteList Component
|
|
*
|
|
* Lists unsigned shipping notes for the current user.
|
|
* Features:
|
|
* - Pull to refresh
|
|
* - Tap to open signature modal
|
|
* - Shows customer, date, note preview
|
|
*/
|
|
|
|
import { shippingNoteApi } from '/mobile/modules/lager/shippingnote/ShippingNoteModule.js';
|
|
|
|
export default {
|
|
name: 'ShippingNoteList',
|
|
emits: ['sign', 'toast'],
|
|
props: {
|
|
user: Object
|
|
},
|
|
|
|
setup(props, { emit }) {
|
|
const { ref, onMounted } = Vue;
|
|
|
|
// Data
|
|
const shippingNotes = ref([]);
|
|
const loading = ref(true);
|
|
const refreshing = ref(false);
|
|
const error = ref(null);
|
|
|
|
// Load shipping notes
|
|
const loadShippingNotes = async (isRefresh = false) => {
|
|
if (isRefresh) {
|
|
refreshing.value = true;
|
|
} else {
|
|
loading.value = true;
|
|
}
|
|
error.value = null;
|
|
|
|
try {
|
|
const data = await shippingNoteApi.get('getMyShippingNotes');
|
|
|
|
if (data.success) {
|
|
shippingNotes.value = data.shippingNotes || [];
|
|
} else {
|
|
error.value = data.error || 'Fehler beim Laden';
|
|
}
|
|
} catch (e) {
|
|
console.error('Failed to load shipping notes:', e);
|
|
error.value = 'Netzwerkfehler';
|
|
} finally {
|
|
loading.value = false;
|
|
refreshing.value = false;
|
|
}
|
|
};
|
|
|
|
// Pull to refresh
|
|
let touchStartY = 0;
|
|
let isPulling = false;
|
|
|
|
const handleTouchStart = (e) => {
|
|
const scrollTop = e.currentTarget.scrollTop;
|
|
if (scrollTop === 0) {
|
|
touchStartY = e.touches[0].clientY;
|
|
isPulling = true;
|
|
}
|
|
};
|
|
|
|
const handleTouchMove = (e) => {
|
|
if (!isPulling) return;
|
|
const deltaY = e.touches[0].clientY - touchStartY;
|
|
if (deltaY > 80 && !refreshing.value) {
|
|
loadShippingNotes(true);
|
|
isPulling = false;
|
|
}
|
|
};
|
|
|
|
const handleTouchEnd = () => {
|
|
isPulling = false;
|
|
};
|
|
|
|
// Open signature for a shipping note
|
|
const openSignature = (shippingNote) => {
|
|
emit('sign', shippingNote);
|
|
navigator.vibrate?.([50]);
|
|
};
|
|
|
|
// Format date
|
|
const formatDate = (dateStr) => {
|
|
if (!dateStr) return '';
|
|
const date = new Date(dateStr);
|
|
return date.toLocaleDateString('de-AT', {
|
|
day: '2-digit',
|
|
month: '2-digit',
|
|
year: 'numeric'
|
|
});
|
|
};
|
|
|
|
// Initialize
|
|
onMounted(() => {
|
|
loadShippingNotes();
|
|
});
|
|
|
|
return {
|
|
shippingNotes,
|
|
loading,
|
|
refreshing,
|
|
error,
|
|
loadShippingNotes,
|
|
handleTouchStart,
|
|
handleTouchMove,
|
|
handleTouchEnd,
|
|
openSignature,
|
|
formatDate
|
|
};
|
|
},
|
|
|
|
template: `
|
|
<div
|
|
class="h-full overflow-y-auto"
|
|
@touchstart="handleTouchStart"
|
|
@touchmove="handleTouchMove"
|
|
@touchend="handleTouchEnd"
|
|
>
|
|
<!-- Pull to refresh indicator -->
|
|
<div v-if="refreshing" class="flex items-center justify-center py-4">
|
|
<svg class="animate-spin h-6 w-6 text-primary" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
</svg>
|
|
</div>
|
|
|
|
<!-- Loading state -->
|
|
<div v-if="loading && !refreshing" class="flex flex-col items-center justify-center py-16">
|
|
<svg class="animate-spin h-8 w-8 text-primary mb-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
</svg>
|
|
<p class="text-sm text-slate-500 dark:text-slate-400">Lade Lieferscheine...</p>
|
|
</div>
|
|
|
|
<!-- Error state -->
|
|
<div v-else-if="error" class="flex flex-col items-center justify-center py-16 px-4">
|
|
<div class="w-16 h-16 rounded-full bg-red-100 dark:bg-red-900/30 flex items-center justify-center mb-4">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
</svg>
|
|
</div>
|
|
<p class="text-sm text-slate-500 dark:text-slate-400 mb-4">{{ error }}</p>
|
|
<button
|
|
@click="loadShippingNotes()"
|
|
class="px-4 py-2 bg-primary text-white rounded-lg text-sm font-medium"
|
|
>
|
|
Erneut versuchen
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Empty state -->
|
|
<div v-else-if="shippingNotes.length === 0" class="flex flex-col items-center justify-center py-16 px-4">
|
|
<div class="w-16 h-16 rounded-full bg-green-100 dark:bg-green-900/30 flex items-center justify-center mb-4">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
</div>
|
|
<p class="text-lg font-medium text-slate-800 dark:text-white mb-1">Alles unterschrieben!</p>
|
|
<p class="text-sm text-slate-500 dark:text-slate-400 text-center">
|
|
Keine offenen Lieferscheine zum Unterschreiben.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Shipping note list -->
|
|
<div v-else class="p-3 space-y-2">
|
|
<div class="text-xs text-slate-500 dark:text-slate-400 mb-2 px-1">
|
|
{{ shippingNotes.length }} {{ shippingNotes.length === 1 ? 'Lieferschein' : 'Lieferscheine' }} zum Unterschreiben
|
|
</div>
|
|
|
|
<button
|
|
v-for="note in shippingNotes"
|
|
:key="note.id"
|
|
@click="openSignature(note)"
|
|
class="w-full text-left bg-white dark:bg-slate-800 rounded-xl p-4 shadow-sm active:bg-slate-50 dark:active:bg-slate-700 transition"
|
|
>
|
|
<div class="flex items-start justify-between">
|
|
<div class="flex-1 min-w-0">
|
|
<!-- Number and Date -->
|
|
<div class="flex items-center gap-2 mb-1">
|
|
<span class="text-sm font-semibold text-primary">
|
|
#{{ note.number || note.id }}
|
|
</span>
|
|
<span class="text-xs text-slate-400 dark:text-slate-500">
|
|
{{ formatDate(note.date) }}
|
|
</span>
|
|
</div>
|
|
|
|
<!-- Customer Name -->
|
|
<div class="font-medium text-slate-800 dark:text-white truncate">
|
|
{{ note.customerName || note.deliveryAddressName || 'Unbekannter Kunde' }}
|
|
</div>
|
|
|
|
<!-- Address -->
|
|
<div class="text-sm text-slate-500 dark:text-slate-400 truncate mt-0.5">
|
|
{{ note.deliveryAddressLine }}, {{ note.deliveryAddressPLZ }} {{ note.deliveryAddressCity }}
|
|
</div>
|
|
|
|
<!-- Note preview -->
|
|
<div v-if="note.note" class="text-xs text-slate-400 dark:text-slate-500 mt-2 truncate">
|
|
{{ note.note }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sign indicator -->
|
|
<div class="flex-shrink-0 ml-3">
|
|
<div class="w-10 h-10 rounded-full bg-amber-100 dark:bg-amber-900/30 flex items-center justify-center">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-amber-600 dark:text-amber-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Refresh hint -->
|
|
<div class="text-center py-4 text-xs text-slate-400 dark:text-slate-500">
|
|
Ziehen zum Aktualisieren
|
|
</div>
|
|
</div>
|
|
`
|
|
};
|