refaktoryzacja komponentów: dodanie interakcji i poprawa wyświetlania specyfikacji produktów
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user