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

View File

@@ -3,7 +3,7 @@
<header class="header">
<div>
<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>
<q-btn
@@ -16,34 +16,31 @@
/>
</header>
<section class="spec">
<div class="spec-header">
<q-icon name="label" class="spec-icon" />
<h4 class="spec-title">tadada</h4>
<q-inner-loading :showing="isLoading" />
<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 class="spec-details">
<div class="spec-record">
<span class="spec-label">Radius:</span>
<span class="spec-value">16"</span>
<template v-else>
<div v-for="section in sections" :key="section.key" class="spec-section">
<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 class="spec-record">
<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>
</template>
</section>
<footer class="drawer-panel__footer">
@@ -54,6 +51,11 @@
</template>
<script setup>
import { computed, watch } from 'vue'
import { useProductSpecificationStore } from 'src/stores/productSpecificationStore'
const productSpecificationStore = useProductSpecificationStore()
const props = defineProps({
productId: {
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'])
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() {
emit('saved', {
productId: props.productId,
@@ -107,7 +143,22 @@ function saveSpecification() {
}
.spec {
position: relative;
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 {
display: flex;
justify-content: start;
@@ -167,7 +218,7 @@ function saveSpecification() {
color: var(--my-on-surface-variant);
}
.footer {
.drawer-panel__footer {
display: flex;
justify-content: flex-end;
gap: 0.75rem;

View File

@@ -2,14 +2,13 @@
<q-layout view="lHh Lpr lFf">
<q-header>
<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">
{{ theme ? 'DARK' : 'LIGHT' }}
</q-btn>
<div>Quasar v{{ $q.version }}</div>
</q-toolbar>
</q-header>
@@ -106,13 +105,14 @@ const rightDrawerOpen = computed({
},
})
function toggleLeftDrawer() {
leftDrawerOpen.value = !leftDrawerOpen.value
}
// function toggleLeftDrawer() {
// leftDrawerOpen.value = !leftDrawerOpen.value
// }
</script>
<style lang="scss" scoped>
.topbar {
background: var(--my-background);
color: var(--my-on-background);
}
</style>

View File

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