9a269aa54f
- Update equipment model with Rentman-compatible fields - Add/Update equipment router endpoints for sync operations - Enhance rentman_service with full API client logic - Improve sync_service for bidirectional equipment sync - Update docker-compose with Rentman env vars - Update frontend useApi composable and nuxt config - Update mietkatalog pages with Rentman-integrated data display - Add images/ to .gitignore (binary assets, 36MB)
65 lines
5.2 KiB
Vue
65 lines
5.2 KiB
Vue
<template>
|
||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||
<nav class="text-sm mb-6" aria-label="Breadcrumb"><NuxtLink to="/mietkatalog" style="color: var(--secondary)">← Zurück zum Katalog</NuxtLink></nav>
|
||
<div v-if="pending" class="grid lg:grid-cols-2 gap-8" aria-live="polite" aria-busy="true"><div class="hms-skeleton aspect-square rounded-lg"></div><div class="space-y-4"><div class="hms-skeleton h-8 w-3/4"></div><div class="hms-skeleton h-4 w-1/4"></div><div class="hms-skeleton h-24 w-full"></div><div class="hms-skeleton h-32 w-full"></div><div class="hms-skeleton h-12 w-full" style="border-radius:var(--radius-md)"></div></div></div>
|
||
<ErrorState v-else-if="error" title="Gerät nicht gefunden" message="Das angeforderte Gerät konnte nicht gefunden werden." @retry="navigate('/mietkatalog')" />
|
||
<div v-else-if="item">
|
||
<div class="grid lg:grid-cols-2 gap-8 lg:gap-12">
|
||
<div class="aspect-square rounded-lg flex items-center justify-center relative overflow-hidden border" :style="{ background: 'var(--surface)', borderColor: 'var(--border)' }">
|
||
<img v-if="item.image_url" :src="item.image_url" :alt="item.name" class="absolute inset-0 w-full h-full object-cover" />
|
||
<div v-else class="text-center"><div class="text-8xl mb-2" style="color: var(--secondary)">📦</div><div class="text-sm" style="color: var(--secondary)">Kein Bild verfügbar</div></div>
|
||
<span v-if="item.category" class="hms-badge hms-badge-primary absolute top-4 left-4 text-sm">{{ item.category }}</span>
|
||
</div>
|
||
<div>
|
||
<div class="text-sm mb-1" style="color: var(--secondary)">Artikelnummer: {{ item.number || item.rentman_id }}</div>
|
||
<h1 class="text-2xl sm:text-3xl font-bold mb-2" style="color: var(--text)">{{ item.name }}</h1>
|
||
<div v-if="item.brand" class="text-sm mb-4" style="color: var(--text-muted)">Marke: <span class="font-medium" style="color: var(--text)">{{ item.brand }}</span></div>
|
||
<p v-if="item.description" class="leading-relaxed mb-6" style="color: var(--text-muted)">{{ item.description }}</p>
|
||
<div v-if="item.specifications" class="hms-card p-4 mb-6"><h2 class="text-sm font-semibold mb-3" style="color: var(--text)">Technische Daten</h2><dl class="divide-y" :style="{ borderColor: 'var(--border)' }"><div v-for="(value, key) in item.specifications" :key="key" class="flex justify-between py-2 text-sm" :style="{ borderColor: 'var(--border)' }"><dt class="capitalize" style="color: var(--secondary)">{{ key.replace(/_/g, ' ') }}</dt><dd class="font-medium text-right" style="color: var(--text)">{{ value }}</dd></div></dl></div>
|
||
<div class="hms-card p-6">
|
||
<h2 class="text-sm font-semibold mb-4" style="color: var(--text)">Mietanfrage</h2>
|
||
<div class="grid grid-cols-2 gap-3 mb-4"><div><label for="rental-start" class="block text-xs font-medium mb-1" style="color: var(--secondary)">Mietbeginn</label><input id="rental-start" v-model="rentalStart" type="date" class="hms-input text-sm" /></div><div><label for="rental-end" class="block text-xs font-medium mb-1" style="color: var(--secondary)">Mietende</label><input id="rental-end" v-model="rentalEnd" type="date" class="hms-input text-sm" /></div></div>
|
||
<div class="mb-4"><label for="quantity" class="block text-xs font-medium mb-1" style="color: var(--secondary)">Anzahl</label><div class="flex items-center gap-3"><button @click="quantity = Math.max(1, quantity - 1)" class="hms-btn hms-btn-secondary px-3 py-2" aria-label="Anzahl verringern">−</button><input id="quantity" v-model.number="quantity" type="number" min="1" class="hms-input text-center w-20" aria-label="Anzahl" /><button @click="quantity = quantity + 1" class="hms-btn hms-btn-secondary px-3 py-2" aria-label="Anzahl erhöhen">+</button></div></div>
|
||
<button @click="addToCart" class="hms-btn hms-btn-primary w-full py-3 text-base"><svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"/></svg>Zur Mietanfrage hinzufügen</button>
|
||
<p class="text-xs mt-3 text-center" style="color: var(--secondary)">Preise auf Anfrage – unverbindliche Mietanfrage</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref } from 'vue'
|
||
import { useRoute } from 'vue-router'
|
||
import { useEquipment } from '~/composables/useEquipment'
|
||
import { useCart } from '~/composables/useCart'
|
||
|
||
const route = useRoute()
|
||
const { detail } = useEquipment()
|
||
const { addItemByFields } = useCart()
|
||
|
||
const quantity = ref(1)
|
||
const rentalStart = ref('')
|
||
const rentalEnd = ref('')
|
||
|
||
const { data: item, pending, error } = await useAsyncData(
|
||
'equipment-detail',
|
||
() => detail(route.params.id as string)
|
||
)
|
||
|
||
function navigate(route: string) {
|
||
navigateTo(route)
|
||
if (import.meta.client) window.scrollTo(0, 0)
|
||
}
|
||
function addToCart() {
|
||
addItemByFields({
|
||
equipment_id: item.value!.id,
|
||
name: item.value!.name,
|
||
rental_price: null,
|
||
image_url: item.value!.image_url || null
|
||
})
|
||
navigate('/warenkorb')
|
||
}
|
||
</script>
|