Files
duck-prod-manager/frontend/src/pages/IndexPage.vue

498 lines
10 KiB
Vue

<template>
<q-page class="content">
<section class="drawer-test-actions">
<q-btn color="primary" label="Open Advanced Search" unelevated @click="openAdvancedSearch" />
<q-btn
color="secondary"
label="Open Product Specification"
unelevated
@click="openProductSpecification"
/>
<q-btn
color="accent"
label="Open Production Statuses"
unelevated
@click="openProductionStatuses"
/>
</section>
<section class="filters">
<div class="search-field">
<input
v-model="searchQuery"
class="input"
placeholder="Search orders or models..."
type="text"
@keyup.enter="applySearch"
/>
<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>
<div class="month-tabs no-scrollbar">
<button
v-for="month in monthFilters"
:key="month.value"
class="tab"
:class="{ active: activeMonth === month.value }"
@click="selectMonth(month.value)"
>
{{ month.label }}
</button>
</div>
</section>
<div class="stats">
<div class="stat-card primary">
<span class="label">In Progress</span>
<span class="value">{{ loadedProductsCount }} Units</span>
</div>
<div class="stat-card tertiary">
<span class="label">Avg Lead Time</span>
<span class="value">22 Days</span>
</div>
</div>
<div class="orders">
<order-card
v-for="product in productsStore.items"
:key="product.id"
:product="product"
@add-production-event="openProductionStatuses"
@show-product-spec="openProductSpecification"
/>
</div>
</q-page>
</template>
<script setup>
import { computed, onMounted, ref, watch } from 'vue'
import OrderCard from 'src/components/OrderCard.vue'
import { UI_PANELS, useUiStore } from 'src/stores/uiStore'
import { useProductsStore } from 'src/stores/productsStore'
const productsStore = useProductsStore()
const uiStore = useUiStore()
const searchQuery = ref(productsStore.searchQuery)
const activeMonth = ref('all')
const monthFilters = [
{ label: 'All', value: 'all' },
{ label: 'Jan', value: 1 },
{ label: 'Feb', value: 2 },
{ label: 'Mar', value: 3 },
{ label: 'Apr', value: 4 },
{ label: 'May', value: 5 },
{ label: 'Jun', value: 6 },
]
const loadedProductsCount = computed(() => productsStore.count)
onMounted(() => {
productsStore.fetchFirstPage()
})
watch(searchQuery, (value) => {
productsStore.setSearchQuery(value)
})
function openAdvancedSearch() {
uiStore.openDrawer(UI_PANELS.ADVANCED_SEARCH, {
source: 'index-page',
})
}
function openProductSpecification(product) {
const selectedProduct = product ?? productsStore.items[0]
console.log(selectedProduct)
if (!selectedProduct) {
return
}
uiStore.openDrawer(UI_PANELS.PRODUCT_SPECIFICATION, {
productId: selectedProduct.id,
mode: 'edit',
})
}
function openProductionStatuses({ product, partType } = {}) {
const selectedProduct = product ?? productsStore.items[0]
if (!selectedProduct) {
return
}
uiStore.openDrawer(UI_PANELS.PRODUCTION_STATUSES, {
orderId: selectedProduct.orderId,
productId: selectedProduct.id,
partType,
})
}
function clearSearch() {
searchQuery.value = ''
productsStore.applySearch(searchQuery.value)
}
function applySearch() {
productsStore.applySearch(searchQuery.value)
}
function selectMonth(month) {
activeMonth.value = month
}
</script>
<style lang="scss" scoped>
@mixin meta-label {
font-size: 0.625rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
}
.content {
padding: 1rem;
}
.drawer-test-actions {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin-bottom: 1.5rem;
}
.filters {
display: grid;
gap: 1rem;
margin-bottom: 1.5rem;
.search-field {
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%;
min-width: 0;
padding: 0.75rem 0.5rem 0.75rem 1rem;
color: var(--my-on-surface);
background: transparent;
border: 0;
&:focus {
outline: none;
}
&::placeholder {
color: var(--my-outline);
}
}
.icon-wrap {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0 1rem 0 0.5rem;
color: var(--my-on-surface-variant);
.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;
}
}
}
.month-tabs {
display: flex;
gap: 0.5rem;
padding-bottom: 0.5rem;
overflow-x: auto;
}
.tab {
flex: 0 0 auto;
padding: 0.5rem 1rem;
color: var(--my-on-surface-variant);
background: var(--my-surface-container);
border-radius: var(--my-radius-md);
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.05em;
text-transform: uppercase;
transition:
background-color var(--my-transition),
color var(--my-transition),
transform var(--my-transition);
&:hover {
background: var(--my-surface-container-high);
}
&:active {
transform: scale(0.95);
}
&.active {
color: var(--my-on-primary);
background: var(--my-primary-dim);
}
}
}
.stats {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.75rem;
margin-bottom: 1rem;
.stat-card {
padding: 1rem;
background: var(--my-surface-container-low);
border-left: 2px solid var(--my-primary);
border-radius: var(--my-radius-lg);
.label {
font-size: 0.625rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
display: block;
margin-bottom: 0.25rem;
color: var(--my-on-surface-variant);
}
.value {
color: var(--my-white);
font-size: 1.5rem;
font-weight: 800;
letter-spacing: -0.025em;
}
&.tertiary {
border-left-color: var(--my-tertiary);
}
}
}
.orders {
display: grid;
gap: 1rem;
}
.order-card {
padding: 1.25rem;
background: var(--my-surface-container);
border-bottom: 2px solid transparent;
border-radius: var(--my-radius-lg);
transition:
background-color var(--my-transition),
border-color var(--my-transition);
&:hover {
background: var(--my-surface-container-high);
border-bottom-color: color-mix(in srgb, var(--my-primary) 30%, transparent);
}
&.urgent {
border-bottom-color: var(--my-error-dim);
}
.card-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1rem;
}
.copy {
display: grid;
gap: 0.25rem;
}
.order-id {
display: block;
color: var(--my-primary);
font-size: 0.625rem;
font-weight: 700;
letter-spacing: -0.05em;
&.order-id-error {
color: var(--my-error);
}
}
.client {
margin: 0;
color: var(--my-on-surface);
font-size: 1.125rem;
font-weight: 700;
line-height: 1.75rem;
letter-spacing: -0.025em;
}
.model {
margin: 0;
color: var(--my-on-surface-variant);
font-size: 0.875rem;
font-weight: 500;
}
.meta {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 0.5rem;
}
.month {
padding: 0.25rem 0.5rem;
color: var(--my-tertiary);
background: var(--my-surface-container-highest);
border-radius: var(--my-radius-sm);
font-size: 0.625rem;
font-weight: 700;
letter-spacing: -0.05em;
text-transform: uppercase;
&.month-error {
color: var(--my-error);
background: color-mix(in srgb, var(--my-error-container) 20%, transparent);
}
}
.finish {
color: var(--my-on-surface-variant);
text-align: center;
.label {
display: block;
font-size: 0.625rem;
font-weight: 700;
text-transform: uppercase;
}
}
.flow {
margin-top: 1.5rem;
}
.flow-head {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
.label {
@include meta-label;
color: var(--my-on-surface-variant);
}
.line {
flex: 1;
height: 1px;
background: color-mix(in srgb, var(--my-outline-variant) 30%, transparent);
}
}
.steps {
display: flex;
align-items: center;
gap: 0.5rem;
padding-bottom: 0.5rem;
overflow-x: auto;
}
.arrow {
color: var(--my-outline-variant);
font-size: 0.75rem;
}
.step {
display: grid;
place-items: center;
flex: 0 0 auto;
width: 2.5rem;
height: 2.5rem;
background: var(--my-surface-container-highest);
border: 1px solid color-mix(in srgb, var(--my-outline-variant) 30%, transparent);
border-radius: var(--my-radius-sm);
.text {
color: var(--my-on-surface-variant);
font-size: 0.75rem;
font-weight: 700;
}
&.done {
background: color-mix(in srgb, var(--my-primary-dim) 20%, transparent);
border-color: color-mix(in srgb, var(--my-primary) 30%, transparent);
.text {
color: var(--my-primary);
}
}
// &.current {
// animation: pulse-soft 1.8s ease-in-out infinite;
// }
&.add {
background: var(--my-surface-container-lowest);
border-style: dashed;
border-color: var(--my-outline-variant);
transition:
background-color var(--my-transition),
transform var(--my-transition);
&:hover {
background: var(--my-surface-container-highest);
}
&:active {
transform: scale(0.9);
}
}
&.alert {
background: var(--my-error-container);
border-color: var(--my-error);
.text {
color: var(--my-on-error);
}
}
&.waiting {
border-style: dashed;
}
.add-icon,
.wait-icon {
color: var(--my-on-surface-variant);
font-size: 0.875rem;
}
}
}
</style>