refaktoryzacja komponentów: dodanie interakcji i poprawa wyświetlania specyfikacji produktów

This commit is contained in:
2026-05-20 19:26:21 +02:00
parent 93778065ce
commit 2005e327f1
4 changed files with 134 additions and 59 deletions

View File

@@ -2,7 +2,7 @@
<div class="my-order-card"> <div class="my-order-card">
<div class="mark"></div> <div class="mark"></div>
<div class="card-head"> <div class="card-head">
<div class="order-info"> <div class="order-info" @click="emit('showProductSpec', { product })">
<span class="order-id">{{ product.orderId }}</span> <span class="order-id">{{ product.orderId }}</span>
<h3 class="model">{{ product.model }}</h3> <h3 class="model">{{ product.model }}</h3>
<p class="client">{{ product.client }}</p> <p class="client">{{ product.client }}</p>
@@ -41,7 +41,7 @@ defineProps({
}, },
}) })
const emit = defineEmits(['addProductionEvent']) const emit = defineEmits(['addProductionEvent', 'showProductSpec'])
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.my-order-card { .my-order-card {

View File

@@ -3,7 +3,7 @@
<header class="header"> <header class="header">
<div> <div>
<h3 class="header-title">Edit Instrument</h3> <h3 class="header-title">Edit Instrument</h3>
<p class="header-info">0029/2025/12</p> <p class="header-info">{{ spec?.orderId ?? props.productId ?? 'brak produktu' }}</p>
</div> </div>
<q-btn <q-btn
@@ -16,34 +16,31 @@
/> />
</header> </header>
<section class="spec"> <section class="spec">
<div class="spec-header"> <q-inner-loading :showing="isLoading" />
<q-icon name="label" class="spec-icon" />
<h4 class="spec-title">tadada</h4> <q-banner v-if="error" rounded class="spec-state spec-state--error">
Nie udalo sie wczytac specyfikacji produktu.
</q-banner>
<div v-else-if="!sections.length && !isLoading" class="spec-state">
Brak specyfikacji dla tego produktu.
</div> </div>
<div class="spec-details">
<div class="spec-record"> <template v-else>
<span class="spec-label">Radius:</span> <div v-for="section in sections" :key="section.key" class="spec-section">
<span class="spec-value">16"</span> <div class="spec-header">
<q-icon name="label" class="spec-icon" />
<h4 class="spec-title">{{ section.label }}</h4>
</div>
<div class="spec-details">
<div v-for="field in section.fields" :key="field.key" class="spec-record">
<span class="spec-label">{{ field.label }}:</span>
<span class="spec-value">{{ formatValues(field.values) }}</span>
</div>
</div>
</div> </div>
<div class="spec-record"> </template>
<span class="spec-label">Drewno Szyjka:</span>
<span class="spec-value">5ply Wenge/Klon with Carbon Rods</span>
</div>
</div>
<div class="spec-header">
<q-icon name="label" class="spec-icon" />
<h4 class="spec-title">tadada</h4>
</div>
<div class="spec-details">
<div class="spec-record">
<span class="spec-label">Radius:</span>
<span class="spec-value">16"</span>
</div>
<div class="spec-record">
<span class="spec-label">Drewno Szyjka:</span>
<span class="spec-value">5ply Wenge/Klon with Carbon Rods</span>
</div>
</div>
</section> </section>
<footer class="drawer-panel__footer"> <footer class="drawer-panel__footer">
@@ -54,6 +51,11 @@
</template> </template>
<script setup> <script setup>
import { computed, watch } from 'vue'
import { useProductSpecificationStore } from 'src/stores/productSpecificationStore'
const productSpecificationStore = useProductSpecificationStore()
const props = defineProps({ const props = defineProps({
productId: { productId: {
type: [Number, String], type: [Number, String],
@@ -65,8 +67,42 @@ const props = defineProps({
}, },
}) })
const spec = computed(() => {
if (!props.productId) {
return null
}
return productSpecificationStore.getSpecification(props.productId)
})
const sections = computed(() => spec.value?.sections ?? [])
const isLoading = computed(() => spec.value?.isLoading ?? false)
const error = computed(() => spec.value?.error ?? null)
const emit = defineEmits(['cancel', 'close', 'saved']) const emit = defineEmits(['cancel', 'close', 'saved'])
watch(
() => props.productId,
(productId) => {
if (productId) {
void loadSpecification(productId)
}
},
{ immediate: true },
)
async function loadSpecification(productId) {
try {
await productSpecificationStore.fetchSpecification(productId)
} catch {
// Error state is stored in productSpecificationStore and rendered above.
}
}
function formatValues(values) {
return values?.length ? values.join(', ') : '-'
}
function saveSpecification() { function saveSpecification() {
emit('saved', { emit('saved', {
productId: props.productId, productId: props.productId,
@@ -107,7 +143,22 @@ function saveSpecification() {
} }
.spec { .spec {
position: relative;
padding: 1rem; padding: 1rem;
.spec-section {
margin-bottom: 1.5rem;
}
.spec-state {
color: var(--my-on-surface-variant);
}
.spec-state--error {
color: var(--my-error);
background: var(--my-error-container);
}
.spec-header { .spec-header {
display: flex; display: flex;
justify-content: start; justify-content: start;
@@ -167,7 +218,7 @@ function saveSpecification() {
color: var(--my-on-surface-variant); color: var(--my-on-surface-variant);
} }
.footer { .drawer-panel__footer {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
gap: 0.75rem; gap: 0.75rem;

View File

@@ -2,14 +2,13 @@
<q-layout view="lHh Lpr lFf"> <q-layout view="lHh Lpr lFf">
<q-header> <q-header>
<q-toolbar class="topbar"> <q-toolbar class="topbar">
<q-btn flat dense round icon="menu" aria-label="Menu" @click="toggleLeftDrawer" /> <!-- <q-btn flat dense round icon="menu" aria-label="Menu" @click="toggleLeftDrawer" /> -->
<q-toolbar-title> Quasar App </q-toolbar-title> <q-toolbar-title> DuckProductionManager </q-toolbar-title>
<q-btn :color="theme ? 'accent' : 'dark'" @click="theme = !theme"> <q-btn :color="theme ? 'accent' : 'dark'" @click="theme = !theme">
{{ theme ? 'DARK' : 'LIGHT' }} {{ theme ? 'DARK' : 'LIGHT' }}
</q-btn> </q-btn>
<div>Quasar v{{ $q.version }}</div>
</q-toolbar> </q-toolbar>
</q-header> </q-header>
@@ -106,13 +105,14 @@ const rightDrawerOpen = computed({
}, },
}) })
function toggleLeftDrawer() { // function toggleLeftDrawer() {
leftDrawerOpen.value = !leftDrawerOpen.value // leftDrawerOpen.value = !leftDrawerOpen.value
} // }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.topbar { .topbar {
background: var(--my-background); background: var(--my-background);
color: var(--my-on-background);
} }
</style> </style>

View File

@@ -1,12 +1,7 @@
<template> <template>
<q-page class="content"> <q-page class="content">
<section class="drawer-test-actions"> <section class="drawer-test-actions">
<q-btn <q-btn color="primary" label="Open Advanced Search" unelevated @click="openAdvancedSearch" />
color="primary"
label="Open Advanced Search"
unelevated
@click="openAdvancedSearch"
/>
<q-btn <q-btn
color="secondary" color="secondary"
label="Open Product Specification" label="Open Product Specification"
@@ -30,8 +25,11 @@
type="text" type="text"
@keyup.enter="applySearch" @keyup.enter="applySearch"
/> />
<div class="icon-wrap" @click="applySearch"> <div class="icon-wrap">
<span class="material-symbols-outlined" data-icon="tune">tune</span> <q-icon class="icon" name="close" @click="clearSearch" />
<span class="v-line"></span>
<q-icon class="icon" name="camera_alt" />
<q-icon class="icon" name="search" @click="applySearch" />
</div> </div>
</div> </div>
@@ -65,6 +63,7 @@
:key="product.id" :key="product.id"
:product="product" :product="product"
@add-production-event="openProductionStatuses" @add-production-event="openProductionStatuses"
@show-product-spec="openProductSpecification"
/> />
</div> </div>
</q-page> </q-page>
@@ -107,15 +106,15 @@ function openAdvancedSearch() {
}) })
} }
function openProductSpecification() { function openProductSpecification(product) {
const product = productsStore.items[0] const selectedProduct = product ?? productsStore.items[0]
console.log(selectedProduct)
if (!product) { if (!selectedProduct) {
return return
} }
uiStore.openDrawer(UI_PANELS.PRODUCT_SPECIFICATION, { uiStore.openDrawer(UI_PANELS.PRODUCT_SPECIFICATION, {
productId: product.id, productId: selectedProduct.id,
mode: 'edit', mode: 'edit',
}) })
} }
@@ -134,6 +133,11 @@ function openProductionStatuses({ product, partType } = {}) {
}) })
} }
function clearSearch() {
searchQuery.value = ''
productsStore.applySearch(searchQuery.value)
}
function applySearch() { function applySearch() {
productsStore.applySearch(searchQuery.value) productsStore.applySearch(searchQuery.value)
} }
@@ -168,20 +172,28 @@ function selectMonth(month) {
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
.search-field { .search-field {
position: relative; display: grid;
grid-template-columns: minmax(0, 1fr) auto;
align-items: center;
color: var(--my-on-surface);
background: var(--my-surface-container-highest);
border-bottom: 2px solid var(--my-outline-variant);
transition: border-color var(--my-transition);
&:focus-within {
border-bottom-color: var(--my-primary);
}
.input { .input {
width: 100%; width: 100%;
padding: 0.75rem 3rem 0.75rem 1rem; min-width: 0;
padding: 0.75rem 0.5rem 0.75rem 1rem;
color: var(--my-on-surface); color: var(--my-on-surface);
background: var(--my-surface-container-highest); background: transparent;
border: 0; border: 0;
border-bottom: 2px solid var(--my-outline-variant);
transition: border-color var(--my-transition);
&:focus { &:focus {
outline: none; outline: none;
border-bottom-color: var(--my-primary);
} }
&::placeholder { &::placeholder {
@@ -190,11 +202,23 @@ function selectMonth(month) {
} }
.icon-wrap { .icon-wrap {
position: absolute; display: flex;
top: 50%; align-items: center;
right: 1rem; gap: 0.75rem;
padding: 0 1rem 0 0.5rem;
color: var(--my-on-surface-variant); color: var(--my-on-surface-variant);
transform: translateY(-50%);
.v-line {
// align-self: stretch; /* 🔥 kluczowe */
height: 2rem;
width: 1px;
background: var(--my-on-surface-variant);
}
.icon {
// color: var(--my-on-surface);
font-size: 1.5rem;
cursor: pointer;
}
} }
} }