Refactored MaterialEdit.vue to streamline field definitions using v-for, centralized field properties in displayFields, and enhanced input handling for HS codes and tariff rates. Improved property validation and default handling in component props for better maintainability. Updated styles and removed unused CSS for a cleaner layout. Adjusted startCalculation in premiseSingleEdit.js for payload consistency.

This commit is contained in:
Jan 2025-11-16 10:09:58 +01:00
parent a4d0f4f913
commit 35b0b8584d
2 changed files with 102 additions and 162 deletions

View file

@ -1,64 +1,37 @@
<template>
<div class="outer-container">
<div class="container" :class="{ 'responsive': responsive }" @focusout="focusLost">
<div class="field-group" v-if="!hideDescription">
<div class="caption-column">Part number</div>
<!-- Wiederholende Felder als Array definieren und mit v-for rendern -->
<template v-for="field in displayFields" :key="field.name">
<div class="caption-column">{{ field.label }}</div>
<div class="input-column">
<span>{{ partNumber }}</span>
</div>
</div>
<div class="field-group" v-if="!hideDescription">
<div class="caption-column">Description</div>
<div class="input-column">
<span>{{ description }}</span>
</div>
</div>
<div class="field-group">
<div class="caption-column">HS code</div>
<div v-if="!tariffUnlocked" class="input-column">
<span>{{ hsCode }}</span>
</div>
<div v-else class="input-column">
<div class="hs-code-container">
<span v-if="!field.editable">{{ field.value }}</span>
<div v-else class="hs-code-container">
<div class="text-container input-field-tariffrate">
<input ref="hsCodeInput" :value="hsCode" @blur="hsCodeChanged" class="input-field" autocomplete="off"/>
<input
:ref="field.ref"
:value="field.value"
@blur="field.onBlur"
class="input-field"
autocomplete="off"
/>
</div>
</div>
</div>
</div>
<div class="field-group">
<div class="caption-column">Tariff rate [%]</div>
<div v-if="!tariffUnlocked" class="input-column">
<span>{{ tariffRatePercent }}</span>
</div>
<div v-else class="input-column">
<div class="text-container input-field-tariffrate">
<input ref="tariffRateInput" :value="tariffRatePercent" @blur="validateTariffRateEvent"
class="input-field"
autocomplete="off"/>
</div>
</div>
</div>
</template>
</div>
<div>
<div v-if="tariffUnlocked" class="tariff-rate-info-header">
<ph-warning size="24"></ph-warning>
<div>
<div>Tariff rate lookup failed</div>
<div class="tariff-rate-info-headerbox">Please contact a customs expert to obtain HS code and tariff rate.</div>
<div v-if="tariffUnlocked" class="tariff-rate-info-header">
<ph-warning size="24" />
<div>
<div>Tariff rate lookup failed</div>
<div class="tariff-rate-info-headerbox">
Please contact a customs expert to obtain HS code and tariff rate.
</div>
</div>
</div>
</div>
</template>
@ -76,7 +49,6 @@ import {mapStores} from "pinia";
import {parseNumberFromString} from "@/common.js";
import Modal from "@/components/UI/Modal.vue";
import SelectMaterial from "@/components/layout/material/SelectMaterial.vue";
import {useCustomsStore} from "@/store/customs.js";
import Checkbox from "@/components/UI/Checkbox.vue";
export default {
@ -90,24 +62,24 @@ export default {
emits: ["update:tariffRate", "updateMaterial", "update:partNumber", "update:hsCode", "save", "close"],
props: {
description: {
type: [String, null],
required: true,
type: String,
default: null,
},
hsCode: {
required: true,
validator: (value) => value === null || typeof value === 'string'
type: String,
default: null,
},
countryId: {
required: true,
validator: (value) => value === null || typeof value === 'number'
type: Number,
default: null,
},
tariffUnlocked: {
type: Boolean,
required: true,
},
tariffRate: {
required: true,
validator: (value) => value === null || typeof value === 'number'
type: Number,
default: null,
},
partNumber: {
type: String,
@ -124,9 +96,54 @@ export default {
},
computed: {
...mapStores(useMaterialStore),
tariffRatePercent() {
return ((this.tariffRate ?? null) !== null) ? (this.tariffRate * 100).toFixed(2) : '';
return this.tariffRate !== null
? (this.tariffRate * 100).toFixed(2)
: '';
},
displayFields() {
const fields = [];
if (!this.hideDescription) {
fields.push(
{
name: 'partNumber',
label: 'Part number',
value: this.partNumber,
editable: false
},
{
name: 'description',
label: 'Description',
value: this.description,
editable: false
}
);
}
fields.push(
{
name: 'hsCode',
label: 'HS code',
value: this.hsCode,
editable: this.tariffUnlocked,
ref: 'hsCodeInput',
onBlur: this.hsCodeChanged
},
{
name: 'tariffRate',
label: 'Tariff rate [%]',
value: this.tariffRatePercent,
editable: this.tariffUnlocked,
ref: 'tariffRateInput',
onBlur: this.validateTariffRateEvent
}
);
return fields;
}
},
created() {
},
@ -136,37 +153,41 @@ export default {
this.$emit('save', 'material');
}
},
updateInputValue(inputRef, formattedValue) {
this.$nextTick(() => {
if (this.$refs[inputRef] && this.$refs[inputRef].value !== formattedValue) {
this.$refs[inputRef].value = formattedValue;
}
});
},
validateTariffRateEvent(event) {
event.target.value = this.validateTariffRate(event.target.value);
},
validateTariffRate(value) {
const percentValue = parseNumberFromString(value, 2, true);
const validatedPercent = (percentValue === null) ? null : Math.max(0, Math.min(999.99, percentValue));
const validatedDecimal = (validatedPercent === null) ? null : validatedPercent / 100;
const validatedPercent = this.clampValue(percentValue, 0, 999.99);
const validatedDecimal = validatedPercent !== null ? validatedPercent / 100 : null;
if (validatedDecimal !== this.tariffRate) {
this.$emit('update:tariffRate', this.roundNumber(validatedDecimal, 4));
}
return validatedPercent === null ? null : validatedPercent.toFixed(2);
return validatedPercent?.toFixed(2) ?? null;
},
clampValue(value, min, max) {
return value === null ? null : Math.max(min, Math.min(max, value));
},
roundNumber(number, digits) {
const multiple = Math.pow(10, digits);
if (number === null) return null;
const multiple = 10 ** digits;
return Math.round(number * multiple) / multiple;
},
hsCodeChanged(event) {
const currentValue = this.$refs.hsCodeSearchbar.searchQuery;
this.$emit("update:hsCode", event.target.value);
},
const sanitized = event.target.value
.replace(/\D/g, '')
.substring(0, 10);
this.$emit("update:hsCode", sanitized);
event.target.value = sanitized;
}
}
}
</script>
@ -182,15 +203,6 @@ export default {
}
.warning-icon {
color: #002F54;
}
.text-container:hover .warning-icon {
transform: scale(1.02);
}
.hs-code-container {
display: flex;
align-items: center;
@ -207,27 +219,22 @@ export default {
flex: 1 1 auto;
}
/* Responsive Layout für Breiten unter 1500px - nur wenn responsive aktiviert ist */
@media (max-width: 1500px) {
.container.responsive {
grid-template-columns:
minmax(auto, max-content) /* Spalte 1: Label */
minmax(120px, 1fr) /* Spalte 2: Input */
minmax(auto, max-content) /* Spalte 3: Label */
minmax(120px, 1fr) /* Spalte 4: Input/Dropdown */
minmax(auto, max-content) /* Spalte 5: Label */
minmax(120px, 1fr) /* Spalte 6: Input */
minmax(auto, max-content) /* Spalte 7: Label */
minmax(120px, 1fr); /* Spalte 8: Input/Dropdown */
minmax(auto, max-content)
minmax(120px, 1fr)
minmax(auto, max-content)
minmax(120px, 1fr)
minmax(auto, max-content)
minmax(120px, 1fr)
minmax(auto, max-content)
minmax(120px, 1fr);
grid-template-rows: auto;
gap: 1.2rem 1rem;
align-items: center;
}
.container.responsive .field-group {
display: contents;
}
.container.responsive .caption-column {
justify-self: start;
}
@ -236,32 +243,12 @@ export default {
min-width: 0;
}
.container.responsive .field-group {
display: contents;
}
.container.responsive .caption-column {
justify-self: start;
}
.container.responsive .input-column {
min-width: 0;
}
}
/* Optimierung für kleinere Bildschirme unter 1500px - nur wenn responsive aktiviert ist */
@media (max-width: 1500px) {
.container.responsive .input-field-tariffrate {
min-width: auto;
flex: 1 1 auto;
}
}
.field-group {
display: contents;
}
.input-column {
display: flex;
justify-content: space-between;
@ -318,53 +305,6 @@ export default {
position: relative;
}
.tariff-rate-info {
position: absolute;
top: 3rem;
left: -3rem;
display: flex;
flex-direction: column;
justify-content: center;
gap: 1.2rem;
z-index: 5000;
background-color: #fff;
border-radius: 0.8rem;
border: 0.1rem solid #E3EDFF;
visibility: hidden;
opacity: 0;
transform: translateY(-20px);
padding: 1.6rem 2.4rem;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
min-width: 40rem;
}
.input-field-tariffrate:hover .tariff-rate-info {
visibility: visible;
opacity: 1;
transform: translateY(0);
transition: opacity 0.5s ease, transform 0.5s ease;
}
.tariff-rate-info-cell-measure {
font-size: 1.2rem;
font-weight: 500;
color: #002F54;
justify-self: flex-end;
}
.tariff-rate-info-cell-tariffrate {
justify-self: flex-end;
}
.tariff-rate-info-container {
margin-top: 2.4rem;
display: grid;
grid-template-columns: auto 1fr 1fr;
gap: 0.8rem;
font-size: 1.2rem;
font-weight: 400;
}
.tariff-rate-info-headerbox {
color: #002F54;
font-size: 1.2rem;

View file

@ -67,7 +67,7 @@ export const usePremiseSingleEditStore = defineStore('premiseSingleEdit', {
async startCalculation() {
this.calculating = true;
const body = this.premise?.id;
const body = [this.premise?.id];
const url = `${config.backendUrl}/calculation/start/`;
let error = null;