Intermediate commit
This commit is contained in:
parent
27b56bc92d
commit
a40a8c6bb4
22 changed files with 1400 additions and 167 deletions
|
|
@ -4,7 +4,7 @@
|
||||||
ref="trigger"
|
ref="trigger"
|
||||||
class="dropdown-trigger"
|
class="dropdown-trigger"
|
||||||
:class="{ 'dropdown-trigger--open': isOpen}"
|
:class="{ 'dropdown-trigger--open': isOpen}"
|
||||||
@click="toggleDropdown"
|
@click.stop="toggleDropdown"
|
||||||
@keydown="handleTriggerKeydown"
|
@keydown="handleTriggerKeydown"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
>
|
>
|
||||||
|
|
@ -143,6 +143,9 @@ export default {
|
||||||
return this.modelValue === option[this.valueKey]
|
return this.modelValue === option[this.valueKey]
|
||||||
},
|
},
|
||||||
handleClickOutside(event) {
|
handleClickOutside(event) {
|
||||||
|
|
||||||
|
console.log("HANDLE click outside")
|
||||||
|
|
||||||
if (!this.$refs.dropdown?.contains(event.target)) {
|
if (!this.$refs.dropdown?.contains(event.target)) {
|
||||||
this.closeDropdown()
|
this.closeDropdown()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
507
src/frontend/src/components/UI/RouteDropdown.vue
Normal file
507
src/frontend/src/components/UI/RouteDropdown.vue
Normal file
|
|
@ -0,0 +1,507 @@
|
||||||
|
<template>
|
||||||
|
<div class="route-dropdown" ref="dropdown">
|
||||||
|
<button
|
||||||
|
ref="trigger"
|
||||||
|
class="route-dropdown-trigger"
|
||||||
|
:class="{ 'route-dropdown-trigger--open': isOpen}"
|
||||||
|
@click="toggleDropdown"
|
||||||
|
@keydown="handleTriggerKeydown"
|
||||||
|
:disabled="disabled"
|
||||||
|
>
|
||||||
|
<span class="route-dropdown-trigger-content">
|
||||||
|
<component
|
||||||
|
v-if="selectedIcon"
|
||||||
|
:is="selectedIcon"
|
||||||
|
:size="16"
|
||||||
|
class="route-icon"
|
||||||
|
/>
|
||||||
|
<span class="route-dropdown-trigger-text">
|
||||||
|
{{ selectedDisplayText }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="route-dropdown-trigger-warning"><ph-warning-circle v-if="warnD2D" weight="fill"
|
||||||
|
size="16"></ph-warning-circle></span>
|
||||||
|
<svg
|
||||||
|
class="route-dropdown-trigger-icon"
|
||||||
|
:class="{ 'route-dropdown-trigger-icon--rotated': isOpen }"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<polyline points="6,9 12,15 18,9"></polyline>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<transition name="route-dropdown-fade">
|
||||||
|
<ul
|
||||||
|
v-if="isOpen"
|
||||||
|
ref="menu"
|
||||||
|
class="route-dropdown-menu"
|
||||||
|
@keydown="handleMenuKeydown"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<!-- D2D Routing Option -->
|
||||||
|
<li
|
||||||
|
class="route-dropdown-option"
|
||||||
|
:class="{
|
||||||
|
'route-dropdown-option--selected': isD2DSelected,
|
||||||
|
'route-dropdown-option--focused': focusedIndex === -1
|
||||||
|
}"
|
||||||
|
@click="selectD2D"
|
||||||
|
@mouseenter="focusedIndex = -1"
|
||||||
|
>
|
||||||
|
<ph-shipping-container :size="16" class="route-icon"/>
|
||||||
|
<span>D2D routing</span>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Divider -->
|
||||||
|
<li v-if="options.length > 0" class="route-dropdown-divider"></li>
|
||||||
|
|
||||||
|
<!-- Regular Route Options -->
|
||||||
|
<li
|
||||||
|
v-for="(option, index) in options"
|
||||||
|
:key="option[valueKey]"
|
||||||
|
class="route-dropdown-option"
|
||||||
|
:class="{
|
||||||
|
'route-dropdown-option--selected': isSelected(option),
|
||||||
|
'route-dropdown-option--focused': focusedIndex === index
|
||||||
|
}"
|
||||||
|
@click="selectOption(option, $event)"
|
||||||
|
@mouseenter="focusedIndex = index"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="getIconForType(option.type)"
|
||||||
|
:size="16"
|
||||||
|
class="route-icon"
|
||||||
|
/>
|
||||||
|
<span>{{ option[displayKey] }}</span>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li v-if="options.length === 0" class="route-dropdown-option route-dropdown-option--empty">
|
||||||
|
{{ emptyText }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {PhTruck, PhBoat, PhTrain, PhShippingContainer, PhWarningCircle} from '@phosphor-icons/vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'RouteDropdown',
|
||||||
|
components: {
|
||||||
|
PhWarningCircle,
|
||||||
|
PhTruck,
|
||||||
|
PhBoat,
|
||||||
|
PhTrain,
|
||||||
|
PhShippingContainer
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
options: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: [String, Number, Object],
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: 'Select a route'
|
||||||
|
},
|
||||||
|
displayKey: {
|
||||||
|
type: String,
|
||||||
|
default: 'routeDisplayString'
|
||||||
|
},
|
||||||
|
valueKey: {
|
||||||
|
type: String,
|
||||||
|
default: 'routeCompareString'
|
||||||
|
},
|
||||||
|
emptyText: {
|
||||||
|
type: String,
|
||||||
|
default: 'No routes available'
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
showD2dWarn: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['update:modelValue', 'change'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isOpen: false,
|
||||||
|
focusedIndex: -1,
|
||||||
|
D2D_VALUE: 'D2D_ROUTING'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
warnD2D() {
|
||||||
|
return this.isD2DSelected && this.showD2dWarn;
|
||||||
|
},
|
||||||
|
selectedOption() {
|
||||||
|
if (!this.modelValue || this.modelValue === this.D2D_VALUE) return null
|
||||||
|
return this.options?.find(option =>
|
||||||
|
option[this.valueKey] === this.modelValue
|
||||||
|
) ?? null
|
||||||
|
},
|
||||||
|
isD2DSelected() {
|
||||||
|
return this.modelValue === this.D2D_VALUE
|
||||||
|
},
|
||||||
|
selectedDisplayText() {
|
||||||
|
if (this.isD2DSelected) return 'D2D routing'
|
||||||
|
if (this.selectedOption) return this.selectedOption[this.displayKey]
|
||||||
|
return this.placeholder
|
||||||
|
},
|
||||||
|
selectedIcon() {
|
||||||
|
if (this.isD2DSelected) return 'ph-shipping-container'
|
||||||
|
if (this.selectedOption) return this.getIconForType(this.selectedOption.type)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
isOpen(newVal) {
|
||||||
|
if (newVal) {
|
||||||
|
// Warte einen Tick, damit das Menü gerendert ist
|
||||||
|
this.$nextTick(() => {
|
||||||
|
document.addEventListener('click', this.handleClickOutside, true)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
document.removeEventListener('click', this.handleClickOutside, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
document.removeEventListener('click', this.handleClickOutside, true)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getIconForType(type) {
|
||||||
|
const iconMap = {
|
||||||
|
'ROAD': 'ph-truck',
|
||||||
|
'SEA': 'ph-boat',
|
||||||
|
'RAIL': 'ph-train'
|
||||||
|
}
|
||||||
|
return iconMap[type] || null
|
||||||
|
},
|
||||||
|
toggleDropdown(event) {
|
||||||
|
if (this.disabled) return
|
||||||
|
|
||||||
|
event.stopPropagation()
|
||||||
|
this.isOpen = !this.isOpen
|
||||||
|
|
||||||
|
if (this.isOpen) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.menu?.focus()
|
||||||
|
|
||||||
|
if (this.isD2DSelected) {
|
||||||
|
this.focusedIndex = -1
|
||||||
|
} else if (this.selectedOption) {
|
||||||
|
this.focusedIndex = this.options.findIndex(option =>
|
||||||
|
option[this.valueKey] === this.modelValue
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
this.focusedIndex = -1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.focusedIndex = -1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectD2D(event) {
|
||||||
|
event.stopPropagation()
|
||||||
|
this.$emit('update:modelValue', this.D2D_VALUE)
|
||||||
|
this.$emit('change', {type: 'D2D', value: this.D2D_VALUE})
|
||||||
|
this.closeDropdown()
|
||||||
|
this.$refs.trigger.focus()
|
||||||
|
},
|
||||||
|
selectOption(option, event) {
|
||||||
|
event.stopPropagation()
|
||||||
|
this.$emit('update:modelValue', option[this.valueKey])
|
||||||
|
this.$emit('change', option)
|
||||||
|
this.closeDropdown()
|
||||||
|
this.$refs.trigger.focus()
|
||||||
|
},
|
||||||
|
closeDropdown() {
|
||||||
|
this.isOpen = false
|
||||||
|
this.focusedIndex = -1
|
||||||
|
},
|
||||||
|
isSelected(option) {
|
||||||
|
return this.modelValue === option[this.valueKey]
|
||||||
|
},
|
||||||
|
handleClickOutside(event) {
|
||||||
|
|
||||||
|
if (!this.$refs.dropdown?.contains(event.target)) {
|
||||||
|
this.closeDropdown()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleTriggerKeydown(event) {
|
||||||
|
switch (event.key) {
|
||||||
|
case 'Enter':
|
||||||
|
case ' ':
|
||||||
|
case 'ArrowDown':
|
||||||
|
event.preventDefault()
|
||||||
|
if (!this.isOpen) {
|
||||||
|
this.toggleDropdown(event)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'ArrowUp':
|
||||||
|
event.preventDefault()
|
||||||
|
if (!this.isOpen) {
|
||||||
|
this.toggleDropdown(event)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'Escape':
|
||||||
|
if (this.isOpen) {
|
||||||
|
event.preventDefault()
|
||||||
|
this.closeDropdown()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleMenuKeydown(event) {
|
||||||
|
const totalOptions = this.options.length
|
||||||
|
|
||||||
|
switch (event.key) {
|
||||||
|
case 'ArrowDown':
|
||||||
|
event.preventDefault()
|
||||||
|
if (this.focusedIndex === -1) {
|
||||||
|
this.focusedIndex = totalOptions > 0 ? 0 : -1
|
||||||
|
} else {
|
||||||
|
this.focusedIndex = Math.min(this.focusedIndex + 1, totalOptions - 1)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'ArrowUp':
|
||||||
|
event.preventDefault()
|
||||||
|
if (this.focusedIndex === 0) {
|
||||||
|
this.focusedIndex = -1
|
||||||
|
} else if (this.focusedIndex === -1) {
|
||||||
|
this.focusedIndex = -1
|
||||||
|
} else {
|
||||||
|
this.focusedIndex = Math.max(this.focusedIndex - 1, 0)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'Enter':
|
||||||
|
event.preventDefault()
|
||||||
|
if (this.focusedIndex === -1) {
|
||||||
|
this.selectD2D(event)
|
||||||
|
} else if (this.focusedIndex >= 0 && this.options[this.focusedIndex]) {
|
||||||
|
this.selectOption(this.options[this.focusedIndex], event)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'Escape':
|
||||||
|
event.preventDefault()
|
||||||
|
this.closeDropdown()
|
||||||
|
this.$refs.trigger.focus()
|
||||||
|
break
|
||||||
|
case 'Tab':
|
||||||
|
this.closeDropdown()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.route-dropdown {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex: 1 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-dropdown-trigger {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
background: white;
|
||||||
|
border-radius: 0.4rem;
|
||||||
|
padding: 0.6rem 1.2rem;
|
||||||
|
border: 0.2rem solid #E3EDFF;
|
||||||
|
transition: all 0.1s ease;
|
||||||
|
flex: 1 0 auto;
|
||||||
|
font: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-dropdown-trigger:hover {
|
||||||
|
background: #EEF4FF;
|
||||||
|
border: 0.2rem solid #8DB3FE;
|
||||||
|
transform: scale(1.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-dropdown-trigger--open {
|
||||||
|
border: 0.2rem solid #8DB3FE;
|
||||||
|
background: #EEF4FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-dropdown-trigger-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.8rem;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-dropdown-trigger-text {
|
||||||
|
color: #2d3748;
|
||||||
|
font: inherit;
|
||||||
|
letter-spacing: -0.05em;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-dropdown-trigger-warning {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: #BC2B72;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-icon {
|
||||||
|
color: #6b7280;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-dropdown-trigger-icon {
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
color: #718096;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-dropdown-trigger-icon--rotated {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-dropdown-menu {
|
||||||
|
font: inherit;
|
||||||
|
outline: none;
|
||||||
|
list-style: none;
|
||||||
|
color: #2d3748;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: white;
|
||||||
|
border: 0.1rem solid #E3EDFF;
|
||||||
|
border-radius: 0.8rem;
|
||||||
|
box-shadow: 0 0.4rem 0.6rem -0.1rem rgba(0, 0, 0, 0.1);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
z-index: 1000;
|
||||||
|
max-height: 50rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
margin-top: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-dropdown-option {
|
||||||
|
font: inherit;
|
||||||
|
padding: 1.2rem 1.6rem;
|
||||||
|
cursor: pointer;
|
||||||
|
border-bottom: 0.16rem solid #f3f4f6;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
color: #2d3748;
|
||||||
|
background: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.8rem;
|
||||||
|
letter-spacing: -0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.route-dropdown-option:hover,
|
||||||
|
.route-dropdown-option--focused {
|
||||||
|
background-color: rgba(107, 134, 156, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-dropdown-option--selected {
|
||||||
|
color: #2d3748;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-dropdown-option--selected:hover,
|
||||||
|
.route-dropdown-option--selected.route-dropdown-option--focused {
|
||||||
|
color: #2d3748;
|
||||||
|
background-color: rgba(107, 134, 156, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-dropdown-option--empty {
|
||||||
|
color: #001D33;
|
||||||
|
cursor: default;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-dropdown-option--empty:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-dropdown-divider {
|
||||||
|
height: 0.1rem;
|
||||||
|
background-color: #e5e7eb;
|
||||||
|
|
||||||
|
border-bottom: none; /* Diese Zeile hinzufügen */
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-dropdown-option:has(+ .route-dropdown-divider) {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Transition animations */
|
||||||
|
.route-dropdown-fade-enter-active,
|
||||||
|
.route-dropdown-fade-leave-active {
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-dropdown-fade-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-dropdown-fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disabled state */
|
||||||
|
.route-dropdown-trigger:disabled {
|
||||||
|
background: white;
|
||||||
|
cursor: not-allowed;
|
||||||
|
border: 0.2rem solid rgba(227, 237, 255, 0.5);
|
||||||
|
color: rgba(0, 47, 84, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-dropdown-trigger:disabled:hover {
|
||||||
|
border: 0.2rem solid rgba(227, 237, 255, 0.5);
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-dropdown-trigger:disabled .route-dropdown-trigger-text {
|
||||||
|
color: rgba(0, 47, 84, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-dropdown-trigger:disabled .route-dropdown-trigger-icon,
|
||||||
|
.route-dropdown-trigger:disabled .route-icon {
|
||||||
|
color: rgba(113, 128, 150, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.route-dropdown-trigger {
|
||||||
|
padding: 10px 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-dropdown-option {
|
||||||
|
padding: 10px 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -293,7 +293,7 @@ export default {
|
||||||
destinationCheck() {
|
destinationCheck() {
|
||||||
|
|
||||||
if (((this.destinations ?? null) === null) || this.destinations.length === 0)
|
if (((this.destinations ?? null) === null) || this.destinations.length === 0)
|
||||||
return false;
|
return true;
|
||||||
|
|
||||||
return !this.destinations?.some(d => d.annual_amount == null);
|
return !this.destinations?.some(d => d.annual_amount == null);
|
||||||
|
|
||||||
|
|
@ -301,7 +301,7 @@ export default {
|
||||||
routeCheck() {
|
routeCheck() {
|
||||||
|
|
||||||
if (((this.destinations ?? null) === null) || this.destinations.length === 0)
|
if (((this.destinations ?? null) === null) || this.destinations.length === 0)
|
||||||
return false;
|
return true;
|
||||||
|
|
||||||
return this.destinations?.every(d => d.routes?.some((route) => route.is_selected));
|
return this.destinations?.every(d => d.routes?.some((route) => route.is_selected));
|
||||||
},
|
},
|
||||||
|
|
@ -352,7 +352,7 @@ export default {
|
||||||
if (this.isInitialized
|
if (this.isInitialized
|
||||||
&& oldVal === false
|
&& oldVal === false
|
||||||
&& newVal === true
|
&& newVal === true
|
||||||
&& this.initialCheckStates?.destination !== true) {
|
&& this.initialCheckStates?.destination === false) { // Hier war !== true, sollte === false sein
|
||||||
this.showDestinationCheck = true;
|
this.showDestinationCheck = true;
|
||||||
if (this.initialCheckStates) {
|
if (this.initialCheckStates) {
|
||||||
this.initialCheckStates.destination = true;
|
this.initialCheckStates.destination = true;
|
||||||
|
|
@ -363,7 +363,7 @@ export default {
|
||||||
if (this.isInitialized
|
if (this.isInitialized
|
||||||
&& oldVal === false
|
&& oldVal === false
|
||||||
&& newVal === true
|
&& newVal === true
|
||||||
&& this.initialCheckStates?.route !== true) {
|
&& this.initialCheckStates?.route === false) { // Hier war !== true, sollte === false sein
|
||||||
this.showRouteCheck = true;
|
this.showRouteCheck = true;
|
||||||
if (this.initialCheckStates) {
|
if (this.initialCheckStates) {
|
||||||
this.initialCheckStates.route = true;
|
this.initialCheckStates.route = true;
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,22 @@
|
||||||
<template>
|
<template>
|
||||||
|
<div class="apps-container">
|
||||||
|
|
||||||
|
<div class="app-list-header">
|
||||||
|
<div>App</div>
|
||||||
|
<div>Groups</div>
|
||||||
|
<div>Action</div>
|
||||||
|
</div>
|
||||||
|
<div class="app-list">
|
||||||
|
|
||||||
<div class="app-list-header">
|
<app-list-item v-for="app in apps" :app="app" @delete-app="deleteApp"></app-list-item>
|
||||||
<div>App</div>
|
</div>
|
||||||
<div>Groups</div>
|
|
||||||
<div>Action</div>
|
|
||||||
</div>
|
|
||||||
<div class="app-list">
|
|
||||||
|
|
||||||
<app-list-item v-for="app in apps" :app="app" @delete-app="deleteApp"></app-list-item>
|
<modal :state="modalState">
|
||||||
|
<add-app @close="closeModal"></add-app>
|
||||||
|
</modal>
|
||||||
|
<basic-button icon="Plus" @click="modalState = true">New App</basic-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<modal :state="modalState">
|
|
||||||
<add-app @close="closeModal"></add-app>
|
|
||||||
</modal>
|
|
||||||
<basic-button icon="Plus" @click="modalState = true">New App</basic-button>
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -65,6 +67,12 @@ export default {
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
|
.apps-container {
|
||||||
|
padding: 2.4rem;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
.app-list-header {
|
.app-list-header {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 2fr 0.5fr;
|
grid-template-columns: 1fr 2fr 0.5fr;
|
||||||
|
|
|
||||||
|
|
@ -295,6 +295,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.bulk-operations-container {
|
.bulk-operations-container {
|
||||||
|
margin: 2.4rem;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="materials-container">
|
||||||
<table-view ref="tableViewRef" :data-source="fetch" :columns="materialColumns" :page="pagination.page"
|
<table-view ref="tableViewRef" :data-source="fetch" :columns="materialColumns" :page="pagination.page"
|
||||||
:page-size="pageSize" :page-count="pagination.pageCount"
|
:page-size="pageSize" :page-count="pagination.pageCount"
|
||||||
:total-count="pagination.totalCount"></table-view>
|
:total-count="pagination.totalCount"></table-view>
|
||||||
|
|
@ -74,5 +74,8 @@ export default {
|
||||||
|
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.materials-container {
|
||||||
|
padding: 2.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="nodes-container">
|
||||||
<table-view ref="tableViewRef" :data-source="fetch" :columns="nodeColumns" :page="pagination.page"
|
<table-view ref="tableViewRef" :data-source="fetch" :columns="nodeColumns" :page="pagination.page"
|
||||||
:page-size="pageSize" :page-count="pagination.pageCount"
|
:page-size="pageSize" :page-count="pagination.pageCount"
|
||||||
:total-count="pagination.totalCount"></table-view>
|
:total-count="pagination.totalCount"></table-view>
|
||||||
|
|
@ -91,4 +91,8 @@ export default {
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
|
.nodes-container {
|
||||||
|
padding: 2.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -166,6 +166,9 @@ export default {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.properties-container {
|
||||||
|
padding: 2.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
.property-item-enter-from {
|
.property-item-enter-from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
|
||||||
|
|
@ -236,6 +236,7 @@ export default {
|
||||||
.container-rate-container {
|
.container-rate-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
padding: 1.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container-rate-header {
|
.container-rate-header {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="users-container">
|
||||||
<div class="user-list">
|
<div class="user-list">
|
||||||
<table-view ref="tableViewRef" :searchbar="false" :columns="columns" :data-source="fetch" @row-click="selectUser"
|
<table-view ref="tableViewRef" :searchbar="false" :columns="columns" :data-source="fetch" @row-click="selectUser"
|
||||||
:mouse-over="true"></table-view>
|
:mouse-over="true"></table-view>
|
||||||
</div>
|
</div>
|
||||||
<modal :state="showModal">
|
<modal :state="showModal">
|
||||||
<edit-user @close="closeModal" v-model:user="selectedUser" :is-new-user="isNewUser"></edit-user>
|
<edit-user @close="closeModal" v-model:user="selectedUser" :is-new-user="isNewUser"></edit-user>
|
||||||
|
|
@ -119,9 +119,9 @@ export default {
|
||||||
badgeResolver: (value) => {
|
badgeResolver: (value) => {
|
||||||
|
|
||||||
const formattedValues = []
|
const formattedValues = []
|
||||||
value.slice(0,5).forEach(v => formattedValues.push({text: v, variant: "secondary"}));
|
value.slice(0, 5).forEach(v => formattedValues.push({text: v, variant: "secondary"}));
|
||||||
|
|
||||||
if(value.length > 5)
|
if (value.length > 5)
|
||||||
formattedValues.push({text: "...", variant: "secondary"});
|
formattedValues.push({text: "...", variant: "secondary"});
|
||||||
|
|
||||||
return formattedValues;
|
return formattedValues;
|
||||||
|
|
@ -141,6 +141,11 @@ export default {
|
||||||
|
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
|
.users-container {
|
||||||
|
padding: 2.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
.user-list {
|
.user-list {
|
||||||
margin-bottom: 2.4rem;
|
margin-bottom: 2.4rem;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,7 @@ export default {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1.6rem;
|
gap: 1.6rem;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
margin: 1.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.destination-edit-handling-cost-info {
|
.destination-edit-handling-cost-info {
|
||||||
|
|
|
||||||
|
|
@ -197,6 +197,7 @@ export default {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 0; /* Important for flexbox shrinking */
|
min-height: 0; /* Important for flexbox shrinking */
|
||||||
|
margin: 1.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.destination-edit-route-warning {
|
.destination-edit-route-warning {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ import {markRaw} from "vue";
|
||||||
import DestinationMassQuantity from "@/components/layout/edit/destination/mass/DestinationMassQuantity.vue";
|
import DestinationMassQuantity from "@/components/layout/edit/destination/mass/DestinationMassQuantity.vue";
|
||||||
import DestinationMassRoute from "@/components/layout/edit/destination/mass/DestinationMassRoute.vue";
|
import DestinationMassRoute from "@/components/layout/edit/destination/mass/DestinationMassRoute.vue";
|
||||||
import DestinationMassHandlingCost from "@/components/layout/edit/destination/mass/DestinationMassHandlingCost.vue";
|
import DestinationMassHandlingCost from "@/components/layout/edit/destination/mass/DestinationMassHandlingCost.vue";
|
||||||
|
import {mapStores} from "pinia";
|
||||||
|
import {useNotificationStore} from "@/store/notification.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "DestinationMassEdit",
|
name: "DestinationMassEdit",
|
||||||
|
|
@ -31,9 +33,37 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
currentTab: null,
|
currentTab: null,
|
||||||
|
isLoading: [false, false, false],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.updateSpinner();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleTabLoadingQuantity(loading) {
|
||||||
|
this.isLoading[0] = loading;
|
||||||
|
this.updateSpinner();
|
||||||
|
},
|
||||||
|
handleTabLoadingHandling(loading) {
|
||||||
|
this.isLoading[1] = loading;
|
||||||
|
this.updateSpinner();
|
||||||
|
},
|
||||||
|
handleTabLoadingRoutes(loading) {
|
||||||
|
this.isLoading[2] = loading;
|
||||||
|
this.updateSpinner();
|
||||||
|
},
|
||||||
|
updateSpinner() {
|
||||||
|
if (this.isLoading[0] || this.isLoading[1] || this.isLoading[2]) {
|
||||||
|
this.notificationStore.setSpinner("Processing ...");
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.notificationStore.clearSpinner();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
...mapStores(useNotificationStore),
|
||||||
defaultTab() {
|
defaultTab() {
|
||||||
return this.tabsConfig.indexOf(this.tabsConfig.find(t => t.matchType === this.type)) ?? 0;
|
return this.tabsConfig.indexOf(this.tabsConfig.find(t => t.matchType === this.type)) ?? 0;
|
||||||
},
|
},
|
||||||
|
|
@ -42,20 +72,23 @@ export default {
|
||||||
{
|
{
|
||||||
title: 'Annual quantity',
|
title: 'Annual quantity',
|
||||||
component: markRaw(DestinationMassQuantity),
|
component: markRaw(DestinationMassQuantity),
|
||||||
props: {premiseIds: this.premiseIds},
|
props: {premiseIds: this.premiseIds, onLoadingChange: this.handleTabLoadingQuantity},
|
||||||
matchType: 'amount'
|
matchType: 'amount',
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Handling & Repackaging',
|
title: 'Handling & Repackaging',
|
||||||
component: markRaw(DestinationMassHandlingCost),
|
component: markRaw(DestinationMassHandlingCost),
|
||||||
props: {premiseIds: this.premiseIds},
|
props: {premiseIds: this.premiseIds, onLoadingChange: this.handleTabLoadingHandling},
|
||||||
matchType: 'handling'
|
matchType: 'handling',
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Routes',
|
title: 'Routes',
|
||||||
component: markRaw(DestinationMassRoute),
|
component: markRaw(DestinationMassRoute),
|
||||||
props: {premiseIds: this.premiseIds},
|
props: {premiseIds: this.premiseIds, onLoadingChange: this.handleTabLoadingRoutes},
|
||||||
matchType: 'routes'
|
matchType: 'routes',
|
||||||
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
<div class="dest-mass-handling-table-header-destination">Destination</div>
|
<div class="dest-mass-handling-table-header-destination">Destination</div>
|
||||||
<div class="dest-mass-handling-table-header-applier">
|
<div class="dest-mass-handling-table-header-applier">
|
||||||
<icon-button icon="check" :disabled="!someChecked" @click="updateOverallValue"></icon-button>
|
<icon-button icon="check" :disabled="!someChecked" @click="updateOverallValue"></icon-button>
|
||||||
|
<icon-button icon="x" :disabled="!someChecked" @click="dismissChecked"></icon-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="dest-mass-handling-table-header-costs">
|
<div class="dest-mass-handling-table-header-costs">
|
||||||
<div>Handling costs</div>
|
<div>Handling costs</div>
|
||||||
|
|
@ -84,11 +85,15 @@ export default {
|
||||||
premiseIds: {
|
premiseIds: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
onLoadingChange: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
handlingCostMatrix: null,
|
|
||||||
handlingCostActive: false,
|
handlingCostActive: false,
|
||||||
overallDisposalCostValue: null,
|
overallDisposalCostValue: null,
|
||||||
overallRepackagingCostValue: null,
|
overallRepackagingCostValue: null,
|
||||||
|
|
@ -102,7 +107,7 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
...mapStores(useDestinationEditStore, usePremiseEditStore),
|
...mapStores(useDestinationEditStore, usePremiseEditStore),
|
||||||
rows() {
|
rows() {
|
||||||
return this.handlingCostMatrix;
|
return this.destinationEditStore.getHandlingCostMatrix
|
||||||
},
|
},
|
||||||
allChecked() {
|
allChecked() {
|
||||||
return this.rows.every(r => r.selected);
|
return this.rows.every(r => r.selected);
|
||||||
|
|
@ -120,8 +125,14 @@ export default {
|
||||||
return this.isCtrlPressed && !this.isShiftPressed;
|
return this.isCtrlPressed && !this.isShiftPressed;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
async created() {
|
||||||
this.buildMatrix();
|
this.onLoadingChange(true);
|
||||||
|
try {
|
||||||
|
await this.buildMatrix();
|
||||||
|
} finally {
|
||||||
|
this.onLoadingChange(false);
|
||||||
|
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
window.addEventListener('keydown', this.handleKeyDown);
|
window.addEventListener('keydown', this.handleKeyDown);
|
||||||
|
|
@ -153,7 +164,7 @@ export default {
|
||||||
},
|
},
|
||||||
onClickAction(data) {
|
onClickAction(data) {
|
||||||
|
|
||||||
this.handlingCostMatrix.forEach(d => {
|
this.rows.forEach(d => {
|
||||||
d.selected = ((data.column === 'material' && d.material === data.row.material)
|
d.selected = ((data.column === 'material' && d.material === data.row.material)
|
||||||
|| (data.column === 'supplier' && d.supplierId === data.row.supplierId)
|
|| (data.column === 'supplier' && d.supplierId === data.row.supplierId)
|
||||||
|| (data.column === 'destination' && d.destinationNodeId === data.row.destinationNodeId)
|
|| (data.column === 'destination' && d.destinationNodeId === data.row.destinationNodeId)
|
||||||
|
|
@ -190,7 +201,7 @@ export default {
|
||||||
|
|
||||||
if (this.overallHandlingCostValue !== null || this.overallDisposalCostValue !== null || this.overallRepackagingCostValue !== null) {
|
if (this.overallHandlingCostValue !== null || this.overallDisposalCostValue !== null || this.overallRepackagingCostValue !== null) {
|
||||||
|
|
||||||
this.handlingCostMatrix
|
this.rows
|
||||||
.filter(row => row.selected)
|
.filter(row => row.selected)
|
||||||
.forEach(row => {
|
.forEach(row => {
|
||||||
row.handling_costs = this.overallHandlingCostValue ?? row.handling_costs;
|
row.handling_costs = this.overallHandlingCostValue ?? row.handling_costs;
|
||||||
|
|
@ -205,7 +216,10 @@ export default {
|
||||||
this.$forceUpdate();
|
this.$forceUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.handlingCostMatrix.forEach(row => row.selected = false);
|
this.dismissChecked();
|
||||||
|
},
|
||||||
|
dismissChecked() {
|
||||||
|
this.rows.forEach(row => row.selected = false);
|
||||||
this.updateOverallCheckBox();
|
this.updateOverallCheckBox();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -218,12 +232,14 @@ export default {
|
||||||
updateCheckBoxes(value) {
|
updateCheckBoxes(value) {
|
||||||
this.rows?.forEach(r => r.selected = value);
|
this.rows?.forEach(r => r.selected = value);
|
||||||
this.updateOverallCheckBox();
|
this.updateOverallCheckBox();
|
||||||
|
|
||||||
},
|
},
|
||||||
updateOverallCheckBox() {
|
updateOverallCheckBox() {
|
||||||
this.overallCheck = this.rows.every(r => r.selected);
|
this.overallCheck = this.rows.every(r => r.selected);
|
||||||
|
|
||||||
if (!this.overallCheck)
|
if (!this.overallCheck)
|
||||||
this.overallIndeterminate = this.rows.some(r => r.selected);
|
this.overallIndeterminate = this.rows.some(r => r.selected);
|
||||||
|
|
||||||
},
|
},
|
||||||
toNode(node, limit = 5) {
|
toNode(node, limit = 5) {
|
||||||
if (!node)
|
if (!node)
|
||||||
|
|
@ -237,8 +253,8 @@ export default {
|
||||||
return `${useMappingId ? mappingId.replace("_", " ") : (needsShortName ? shortName : name)}`;
|
return `${useMappingId ? mappingId.replace("_", " ") : (needsShortName ? shortName : name)}`;
|
||||||
|
|
||||||
},
|
},
|
||||||
buildMatrix() {
|
async buildMatrix() {
|
||||||
this.handlingCostMatrix = [];
|
const handlingCostMatrix = [];
|
||||||
|
|
||||||
for (const pId of this.premiseIds) {
|
for (const pId of this.premiseIds) {
|
||||||
const premise = this.premiseEditStore.getById(pId);
|
const premise = this.premiseEditStore.getById(pId);
|
||||||
|
|
@ -246,11 +262,12 @@ export default {
|
||||||
if (!destinations) continue;
|
if (!destinations) continue;
|
||||||
|
|
||||||
for (const d of destinations) {
|
for (const d of destinations) {
|
||||||
this.handlingCostMatrix.push({
|
handlingCostMatrix.push({
|
||||||
id: premise.id,
|
id: premise.id,
|
||||||
material: premise.material.part_number,
|
material: premise.material.part_number,
|
||||||
supplier: this.toNode(premise.supplier, 15),
|
supplier: this.toNode(premise.supplier, 15),
|
||||||
supplierId: premise.supplier.id,
|
supplierId: premise.supplier.id,
|
||||||
|
supplierIso: premise.supplier.country.iso_code,
|
||||||
destinationId: d.id,
|
destinationId: d.id,
|
||||||
destinationNodeId: d.destination_node.id,
|
destinationNodeId: d.destination_node.id,
|
||||||
destination: this.toNode(d.destination_node, 15),
|
destination: this.toNode(d.destination_node, 15),
|
||||||
|
|
@ -260,14 +277,13 @@ export default {
|
||||||
selected: false
|
selected: false
|
||||||
});
|
});
|
||||||
|
|
||||||
this.handlingCostActive |= ((d.handling_costs !==null) || d.repackaging_costs !== null || d.disposal_costs !== null);
|
this.handlingCostActive = ((d.handling_costs !== null) || d.repackaging_costs !== null || d.disposal_costs !== null) || this.handlingCostActive;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.destinationEditStore.setHandlingCostMatrix(handlingCostMatrix);
|
||||||
|
await this.$nextTick();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -432,7 +448,8 @@ export default {
|
||||||
.dest-mass-handling-table-header-applier {
|
.dest-mass-handling-table-header-applier {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 5rem;
|
width: 7rem;
|
||||||
|
gap: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dest-mass-handling-table-header-costs {
|
.dest-mass-handling-table-header-costs {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
<div class="dest-mass-handling-row-supplier dest-mass-handling-row__cell--filterable"
|
<div class="dest-mass-handling-row-supplier dest-mass-handling-row__cell--filterable"
|
||||||
@click="action($event,'supplier')"
|
@click="action($event,'supplier')"
|
||||||
@mousedown="handleMouseDown">
|
@mousedown="handleMouseDown">
|
||||||
<ph-factory size="24"/>
|
<flag :iso="row.supplierIso" />
|
||||||
{{ row.supplier }}
|
{{ row.supplier }}
|
||||||
</div>
|
</div>
|
||||||
<div class="dest-mass-handling-row-destination dest-mass-handling-row__cell--filterable"
|
<div class="dest-mass-handling-row-destination dest-mass-handling-row__cell--filterable"
|
||||||
|
|
@ -63,10 +63,11 @@
|
||||||
import Checkbox from "@/components/UI/Checkbox.vue";
|
import Checkbox from "@/components/UI/Checkbox.vue";
|
||||||
import {PhFactory, PhMapPin} from "@phosphor-icons/vue";
|
import {PhFactory, PhMapPin} from "@phosphor-icons/vue";
|
||||||
import {parseNumberFromString} from "@/common.js";
|
import {parseNumberFromString} from "@/common.js";
|
||||||
|
import Flag from "@/components/UI/Flag.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "DestinationMassHandlingCostRow",
|
name: "DestinationMassHandlingCostRow",
|
||||||
components: {PhMapPin, PhFactory, Checkbox},
|
components: {Flag, PhMapPin, PhFactory, Checkbox},
|
||||||
emits: ['action', 'update-selected'],
|
emits: ['action', 'update-selected'],
|
||||||
props: {
|
props: {
|
||||||
row: {
|
row: {
|
||||||
|
|
@ -225,7 +226,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.dest-mass-handling-row-applier {
|
.dest-mass-handling-row-applier {
|
||||||
width: 5rem;
|
width: 7rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dest-mass-handling-row-supplier {
|
.dest-mass-handling-row-supplier {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@
|
||||||
<div class="dest-mass-quantity-table-header-material">Material</div>
|
<div class="dest-mass-quantity-table-header-material">Material</div>
|
||||||
<div class="dest-mass-quantity-table-header-supplier">Supplier</div>
|
<div class="dest-mass-quantity-table-header-supplier">Supplier</div>
|
||||||
<div class="dest-mass-quantity-table-header-applier">
|
<div class="dest-mass-quantity-table-header-applier">
|
||||||
<icon-button icon="check" :disabled="!someChecked" @click="updateOverallValue"></icon-button></div>
|
<icon-button icon="check" :disabled="!someChecked" @click="updateOverallValue"></icon-button>
|
||||||
|
<icon-button icon="x" :disabled="!someChecked" @click="dismissChecked"></icon-button>
|
||||||
|
</div>
|
||||||
<div class="dest-mass-quantity-table-header-dest"
|
<div class="dest-mass-quantity-table-header-dest"
|
||||||
:key="`${dest.id}`"
|
:key="`${dest.id}`"
|
||||||
v-for="dest in destPool">
|
v-for="dest in destPool">
|
||||||
|
|
@ -55,13 +57,17 @@ export default {
|
||||||
premiseIds: {
|
premiseIds: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
onLoadingChange: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
||||||
destPool: null,
|
destPool: null,
|
||||||
destMatrix: null,
|
|
||||||
overallCheck: false,
|
overallCheck: false,
|
||||||
overallIndeterminate: false,
|
overallIndeterminate: false,
|
||||||
isCtrlPressed: false,
|
isCtrlPressed: false,
|
||||||
|
|
@ -71,7 +77,7 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
...mapStores(useDestinationEditStore, usePremiseEditStore),
|
...mapStores(useDestinationEditStore, usePremiseEditStore),
|
||||||
rows() {
|
rows() {
|
||||||
return this.destMatrix;
|
return this.destinationEditStore.getQuantityMatrix;
|
||||||
},
|
},
|
||||||
allChecked() {
|
allChecked() {
|
||||||
return this.rows.every(r => r.selected);
|
return this.rows.every(r => r.selected);
|
||||||
|
|
@ -89,15 +95,13 @@ export default {
|
||||||
return this.isCtrlPressed && !this.isShiftPressed;
|
return this.isCtrlPressed && !this.isShiftPressed;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// watch: {
|
async created() {
|
||||||
// someChecked(newVal, oldVal) {
|
this.onLoadingChange(true);
|
||||||
// if(newVal === true && oldVal === false) {
|
try {
|
||||||
// //reset overall inputs
|
await this.buildMatrix();
|
||||||
// }
|
} finally {
|
||||||
// }
|
this.onLoadingChange(false);
|
||||||
// },
|
}
|
||||||
created() {
|
|
||||||
this.buildMatrix();
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
window.addEventListener('keydown', this.handleKeyDown);
|
window.addEventListener('keydown', this.handleKeyDown);
|
||||||
|
|
@ -131,7 +135,7 @@ export default {
|
||||||
},
|
},
|
||||||
onClickAction(data) {
|
onClickAction(data) {
|
||||||
|
|
||||||
this.destMatrix.forEach(d => {
|
this.rows.forEach(d => {
|
||||||
d.selected = ((data.column === 'material' && d.material === data.row.material)
|
d.selected = ((data.column === 'material' && d.material === data.row.material)
|
||||||
|| (data.column === 'supplier' && d.supplier === data.row.supplier)
|
|| (data.column === 'supplier' && d.supplier === data.row.supplier)
|
||||||
|| (data.action === 'append' && d.selected));
|
|| (data.action === 'append' && d.selected));
|
||||||
|
|
@ -157,7 +161,7 @@ export default {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (updates.length > 0) {
|
if (updates.length > 0) {
|
||||||
this.destMatrix
|
this.rows
|
||||||
.filter(row => row.selected)
|
.filter(row => row.selected)
|
||||||
.forEach(row => {
|
.forEach(row => {
|
||||||
updates.forEach(update => {
|
updates.forEach(update => {
|
||||||
|
|
@ -173,7 +177,10 @@ export default {
|
||||||
this.$forceUpdate();
|
this.$forceUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.destMatrix.forEach(row => row.selected = false);
|
this.dismissChecked();
|
||||||
|
},
|
||||||
|
dismissChecked() {
|
||||||
|
this.rows.forEach(row => row.selected = false);
|
||||||
this.updateOverallCheckBox();
|
this.updateOverallCheckBox();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -205,7 +212,7 @@ export default {
|
||||||
return `${useMappingId ? mappingId.replace("_", " ") : (needsShortName ? shortName : name)}`;
|
return `${useMappingId ? mappingId.replace("_", " ") : (needsShortName ? shortName : name)}`;
|
||||||
|
|
||||||
},
|
},
|
||||||
buildMatrix() {
|
async buildMatrix() {
|
||||||
// destPool aufbauen
|
// destPool aufbauen
|
||||||
const destMap = new Map();
|
const destMap = new Map();
|
||||||
|
|
||||||
|
|
@ -228,8 +235,8 @@ export default {
|
||||||
|
|
||||||
this.destPool = Array.from(destMap.values());
|
this.destPool = Array.from(destMap.values());
|
||||||
|
|
||||||
// destMatrix aufbauen
|
|
||||||
this.destMatrix = this.premiseIds
|
const quantityMatrix = this.premiseIds
|
||||||
.filter(p => p)
|
.filter(p => p)
|
||||||
.map(p => this.premiseEditStore.getById(p))
|
.map(p => this.premiseEditStore.getById(p))
|
||||||
.map(premise => {
|
.map(premise => {
|
||||||
|
|
@ -258,6 +265,9 @@ export default {
|
||||||
selected: false
|
selected: false
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.destinationEditStore.setQuantityMatrix(quantityMatrix);
|
||||||
|
await this.$nextTick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -340,7 +350,7 @@ export default {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dest-mass-quantity-table-wrapper {
|
.dest-mass-quantity-table-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -400,7 +410,8 @@ export default {
|
||||||
.dest-mass-quantity-table-header-applier {
|
.dest-mass-quantity-table-header-applier {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 5rem;
|
width: 7rem;
|
||||||
|
gap: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dest-mass-quantity-table-header-dest {
|
.dest-mass-quantity-table-header-dest {
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.dest-mass-quantity-row-applier {
|
.dest-mass-quantity-row-applier {
|
||||||
width: 5rem;
|
width: 7rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dest-mass-quantity-row-supplier {
|
.dest-mass-quantity-row-supplier {
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,39 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="dest-mass-route-container">
|
<div class="dest-mass-route-container">
|
||||||
<div class="dest-mass-route-table-wrapper">
|
<div v-if="generalError">
|
||||||
<div class="dest-mass-route-table-header">
|
<div class="destination-mass-route-info">
|
||||||
<div class="dest-mass-route-table-header-checkbox">
|
<ph-warning size="18px"></ph-warning>
|
||||||
<checkbox @checkbox-changed="updateCheckBoxes" :checked="overallCheck"
|
The routing data is faulty. Please contact support.
|
||||||
:indeterminate="overallIndeterminate"></checkbox>
|
You can try to solve the problem by first deleting all destinations and then creating them again.
|
||||||
</div>
|
</div>
|
||||||
<div class="dest-mass-route-table-header-supplier">Supplier</div>
|
</div>
|
||||||
<div class="dest-mass-route-table-header-dest"
|
<div v-else class="dest-mass-route-table-wrapper">
|
||||||
:key="`${dest.id}`"
|
<div class="dest-mass-route-table-header-wrapper"
|
||||||
v-for="dest in destPool">
|
ref="headerWrapper"
|
||||||
<div>{{ toNode(dest.destination_node, 6) }}</div>
|
@scroll="syncScroll('header')">
|
||||||
<div class="text-container" :class="{disabled: !someChecked}">
|
<div class="dest-mass-route-table-header">
|
||||||
<input class="input-field"
|
<div class="dest-mass-route-table-header-supplier"></div>
|
||||||
v-model="dest.overallValue"
|
<div class="dest-mass-route-table-header-dest"
|
||||||
autocomplete="off"
|
:key="`${dest.id}`"
|
||||||
@blur="validateAnnualAmount($event, dest)"
|
v-for="dest in destPool">
|
||||||
:disabled="!someChecked"/>
|
<div></div>
|
||||||
|
<div>{{ toNode(dest.destination_node, 6) }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dest-mass-route-table">
|
|
||||||
|
|
||||||
|
<div class="dest-mass-route-table"
|
||||||
|
ref="tableBody"
|
||||||
|
@scroll="syncScroll('body')">
|
||||||
|
<destination-mass-route-row :row="row"
|
||||||
|
:key="row.id"
|
||||||
|
v-for="row in rows"></destination-mass-route-row>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import DestinationMassQuantityRow from "@/components/layout/edit/destination/mass/DestinationMassQuantityRow.vue";
|
import DestinationMassQuantityRow from "@/components/layout/edit/destination/mass/DestinationMassQuantityRow.vue";
|
||||||
|
|
@ -36,30 +43,58 @@ import {mapStores} from "pinia";
|
||||||
import {useDestinationEditStore} from "@/store/destinationEdit.js";
|
import {useDestinationEditStore} from "@/store/destinationEdit.js";
|
||||||
import {usePremiseEditStore} from "@/store/premiseEdit.js";
|
import {usePremiseEditStore} from "@/store/premiseEdit.js";
|
||||||
import {toRaw} from "vue";
|
import {toRaw} from "vue";
|
||||||
|
import DestinationMassRouteRow from "@/components/layout/edit/destination/mass/DestinationMassRouteRow.vue";
|
||||||
|
import Flag from "@/components/UI/Flag.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "DestinationMassRoute",
|
name: "DestinationMassRoute",
|
||||||
components: {IconButton, Checkbox, DestinationMassQuantityRow},
|
components: {Flag, DestinationMassRouteRow, IconButton, Checkbox, DestinationMassQuantityRow},
|
||||||
props: {
|
props: {
|
||||||
premiseIds: {
|
premiseIds: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
onLoadingChange: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapStores(useDestinationEditStore, usePremiseEditStore)
|
...mapStores(useDestinationEditStore, usePremiseEditStore),
|
||||||
|
rows() {
|
||||||
|
return this.destinationEditStore.getRouteMatrix;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
destPool: null,
|
destPool: null,
|
||||||
destMatrix: null,
|
generalError: false,
|
||||||
|
isScrollingSyncronized: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
async created() {
|
||||||
this.buildMatrix();
|
this.onLoadingChange(true);
|
||||||
|
try {
|
||||||
|
await this.buildMatrix();
|
||||||
|
} finally {
|
||||||
|
this.onLoadingChange(false);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
syncScroll(source) {
|
||||||
|
if (this.isScrollingSyncronized) {
|
||||||
|
this.isScrollingSyncronized = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isScrollingSyncronized = true;
|
||||||
|
|
||||||
|
if (source === 'body') {
|
||||||
|
this.$refs.headerWrapper.scrollLeft = this.$refs.tableBody.scrollLeft;
|
||||||
|
} else if (source === 'header') {
|
||||||
|
this.$refs.tableBody.scrollLeft = this.$refs.headerWrapper.scrollLeft;
|
||||||
|
}
|
||||||
|
},
|
||||||
toNode(node, limit = 5) {
|
toNode(node, limit = 5) {
|
||||||
if (!node)
|
if (!node)
|
||||||
return 'N/A';
|
return 'N/A';
|
||||||
|
|
@ -72,26 +107,62 @@ export default {
|
||||||
return `${useMappingId ? mappingId.replace("_", " ") : (needsShortName ? shortName : name)}`;
|
return `${useMappingId ? mappingId.replace("_", " ") : (needsShortName ? shortName : name)}`;
|
||||||
|
|
||||||
},
|
},
|
||||||
buildMatrix() {
|
async buildMatrix() {
|
||||||
const destMap = new Map();
|
const columnHeadersMap = new Map();
|
||||||
|
const supplierToDestinationsMap = new Map();
|
||||||
|
|
||||||
for (const pId of this.premiseIds) {
|
for (const pId of this.premiseIds) {
|
||||||
const destinations = this.destinationEditStore.getByPremiseId(pId);
|
const curPremise = this.premiseEditStore.getById(pId);
|
||||||
if (!destinations) continue;
|
const destOfCurPremise = this.destinationEditStore.getByPremiseId(pId);
|
||||||
|
if (!destOfCurPremise) continue;
|
||||||
|
|
||||||
for (const d of destinations) {
|
/* supplier map collects all destinations for one supplier
|
||||||
|
* and replaces the destination if the same destination is found
|
||||||
|
* that already has a selected route.
|
||||||
|
*/
|
||||||
|
if (!supplierToDestinationsMap.has(curPremise.supplier.id)) {
|
||||||
|
supplierToDestinationsMap.set(curPremise.supplier.id, {
|
||||||
|
destinations: destOfCurPremise ?? []
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const mapEntry = supplierToDestinationsMap.get(curPremise.supplier.id);
|
||||||
|
const exDs = mapEntry.destinations;
|
||||||
|
|
||||||
|
destOfCurPremise.forEach(d => {
|
||||||
|
const exD = exDs.find(ex => ex.destination_node.id === d.destination_node.id) ?? null;
|
||||||
|
|
||||||
|
if (!exD) {
|
||||||
|
exDs.push(d);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if ((!exD.routes?.some(r => r.is_selected) && d.routes?.some(r => r.is_selected) && !exD.is_d2d) || (!exD.is_d2d && d.is_d2d)) {
|
||||||
|
const idx = exDs.indexOf(exD);
|
||||||
|
exDs.splice(idx, 1);
|
||||||
|
exDs.push(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/* destination map collects all destinations over all
|
||||||
|
* suppliers for table headers
|
||||||
|
*/
|
||||||
|
for (const d of destOfCurPremise) {
|
||||||
const destId = d.destination_node.id;
|
const destId = d.destination_node.id;
|
||||||
if (!destMap.has(destId)) {
|
if (!columnHeadersMap.has(destId)) {
|
||||||
destMap.set(destId, {
|
columnHeadersMap.set(destId, {
|
||||||
...d,
|
destination_node: {...d.destination_node},
|
||||||
|
destinationNodeId: d.destination_node.id,
|
||||||
|
destinationNodeName: d.destination_node.name
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.destPool = Array.from(destMap.values());
|
this.destPool = Array.from(columnHeadersMap.values());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// destMatrix aufbauen
|
|
||||||
const premiseMap = new Map();
|
const premiseMap = new Map();
|
||||||
|
|
||||||
this.premiseIds.forEach(pId => {
|
this.premiseIds.forEach(pId => {
|
||||||
|
|
@ -102,8 +173,8 @@ export default {
|
||||||
premiseMap.set(premise.supplier.id, {
|
premiseMap.set(premise.supplier.id, {
|
||||||
ids: [],
|
ids: [],
|
||||||
supplierNodeId: premise.supplier.id,
|
supplierNodeId: premise.supplier.id,
|
||||||
supplier: premise.supplier.name,
|
supplier: premise.supplier,
|
||||||
destinations: []
|
destinations: this.buildDestinations(columnHeadersMap, supplierToDestinationsMap.get(premise.supplier.id)?.destinations ?? [])
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,74 +185,110 @@ export default {
|
||||||
this.addDestinationsToRow(row.destinations, destinations)
|
this.addDestinationsToRow(row.destinations, destinations)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.destMatrix = [];
|
const destMatrix = Array.from(premiseMap.values());
|
||||||
// .filter(p => p)
|
this.generalError = destMatrix.some(r => !r.destinations.every(d => d.valid));
|
||||||
// .map(p => this.premiseEditStore.getById(p))
|
|
||||||
// .map(premise => {
|
|
||||||
// const destRaw = this.destinationEditStore.getByPremiseId(premise.id);
|
|
||||||
//
|
|
||||||
// // Map für schnelleren Lookup erstellen
|
|
||||||
// const destLookup = new Map();
|
|
||||||
// if (destRaw) {
|
|
||||||
// for (const dr of destRaw) {
|
|
||||||
// destLookup.set(dr.destination_node.id, dr);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// console.log("prem", toRaw( premise), destRaw)
|
|
||||||
//
|
|
||||||
// return {
|
|
||||||
// // id: premise.id,
|
|
||||||
// // supplier: this.toNode(premise.supplier, 30),
|
|
||||||
// // destinations: this.destPool.map(dest => {
|
|
||||||
// // const match = destLookup.get(dest.id);
|
|
||||||
// // return {
|
|
||||||
// // annual_amount: match?.annual_amount ?? null,
|
|
||||||
// // id: match?.id ?? null,
|
|
||||||
// // nodeId: dest.id,
|
|
||||||
// // };
|
|
||||||
// // }),
|
|
||||||
// // selected: false
|
|
||||||
// };
|
|
||||||
// });
|
|
||||||
|
|
||||||
|
this.destinationEditStore.setRouteMatrix(destMatrix)
|
||||||
|
await this.$nextTick();
|
||||||
|
},
|
||||||
|
buildDestinations(allDestinationsMap, assignedDestinations) {
|
||||||
|
return Array.from(allDestinationsMap.values()).map(d => {
|
||||||
|
|
||||||
|
const assignedDest = assignedDestinations.find(dest => dest.destination_node.id === d.destinationNodeId);
|
||||||
|
|
||||||
|
const builtRoutes = this.buildRoutes(assignedDest?.routes);
|
||||||
|
const selectedBuildRoute = builtRoutes?.find(r => r.selected)?.routeCompareString ?? null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
ids: [],
|
||||||
|
disabled: true,
|
||||||
|
valid: true,
|
||||||
|
destinationNodeId: d.destinationNodeId,
|
||||||
|
destinationName: d.destinationNodeName,
|
||||||
|
routes: builtRoutes,
|
||||||
|
isD2d: assignedDest?.is_d2d ?? false,
|
||||||
|
rateD2d: assignedDest?.rate_d2d ?? null,
|
||||||
|
leadTimeD2d: assignedDest?.lead_time_d2d === 0 ? null : (assignedDest?.lead_time_d2d ?? null),
|
||||||
|
selectedRoute: selectedBuildRoute
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
addDestinationsToRow(rowDestinations, premiseDestinations) {
|
addDestinationsToRow(rowDestinations, premiseDestinations) {
|
||||||
premiseDestinations.forEach(premD => {
|
premiseDestinations.forEach(premD => {
|
||||||
|
|
||||||
let existingDest = rowDestinations.find(rowD => rowD.destinationNodeId === premD.destination_node.id) ?? null;
|
let existingDest = rowDestinations.find(rowD => rowD.destinationNodeId === premD.destination_node.id) ?? null;
|
||||||
|
|
||||||
/* create destination, if it does not exist for supplier. */
|
if (existingDest) {
|
||||||
if (!existingDest) {
|
existingDest.disabled = false;
|
||||||
|
existingDest.ids.push(premD.id);
|
||||||
|
|
||||||
existingDest = {
|
/* add route ids to routes */
|
||||||
ids: [],
|
this.verifyRoutes(existingDest, premD)
|
||||||
destinationNodeId: premD.destination_node.id,
|
|
||||||
destinationName: premD.destination_node.name,
|
|
||||||
routes: this.buildRoutes(premD.routes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// premdD.routes.push
|
|
||||||
rowDestinations.push(existingDest);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* add orig Destination id to destination */
|
|
||||||
existingDest.ids.push(premD.id);
|
|
||||||
|
|
||||||
/* add route ids to routes */
|
|
||||||
this.addRoutesToDestination(existingDest.routes, premD.routes)
|
|
||||||
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
addRoutesToDestination(rowRoutes, premiseRoutes) {
|
verifyRoutes(rowDest, premiseDest) {
|
||||||
// premiseRoutes
|
|
||||||
|
|
||||||
|
const premiseRoutes = premiseDest.routes;
|
||||||
|
|
||||||
|
if (rowDest.routes.length !== premiseRoutes.length) {
|
||||||
|
console.log("length mismatch ", toRaw(rowDest), toRaw(premiseDest));
|
||||||
|
rowDest.valid = false;
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
premiseRoutes.forEach(route => {
|
||||||
|
const routeString = JSON.stringify(route.transit_nodes.map(n => n.external_mapping_id)); //.join(" > ").replace("_", " ");
|
||||||
|
|
||||||
|
if (!(rowDest.routes.some(r => r.routeCompareString === routeString && r.type === route.type))) {
|
||||||
|
console.log("no matching route ", routeString, rowDest);
|
||||||
|
rowDest.valid = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
buildRoutes(routes) {
|
buildRoutes(routes) {
|
||||||
|
|
||||||
|
return routes?.map(r => {
|
||||||
|
return {
|
||||||
|
type: r.type,
|
||||||
|
selected: r.is_selected,
|
||||||
|
transitNodes: r.transit_nodes.map(n => n.external_mapping_id),
|
||||||
|
routeCompareString: JSON.stringify(r.transit_nodes.map(n => n.external_mapping_id)), //.join(" > ").replace("_", " ")
|
||||||
|
routeDisplayString: this.toRoute(r)
|
||||||
|
}
|
||||||
|
}) ?? [];
|
||||||
|
},
|
||||||
|
toRoute(route, limit = 48) {
|
||||||
|
|
||||||
|
if (!route)
|
||||||
|
return 'N/A';
|
||||||
|
|
||||||
|
const nodes = route.transit_nodes?.map((node) => this.toNode(node)) ?? [];
|
||||||
|
|
||||||
|
if (nodes.length === 0)
|
||||||
|
return 'N/A';
|
||||||
|
|
||||||
|
const separator = " > ";
|
||||||
|
let fullString = nodes.join(separator);
|
||||||
|
|
||||||
|
if (fullString.length <= limit)
|
||||||
|
return fullString;
|
||||||
|
|
||||||
|
const front = nodes[0].concat(separator).concat("...").concat(separator);
|
||||||
|
let back = [];
|
||||||
|
|
||||||
|
for (const node of nodes.slice().reverse()) {
|
||||||
|
|
||||||
|
back.unshift(node);
|
||||||
|
const temp = front.concat(back.join(separator));
|
||||||
|
|
||||||
|
if (temp.length > limit) {
|
||||||
|
return front.concat(back.slice(1).join(separator));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -190,4 +297,160 @@ export default {
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
|
.destination-mass-route-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
gap: 1.6rem;
|
||||||
|
background-color: #BC2B72;
|
||||||
|
color: #ffffff;
|
||||||
|
border-radius: 0.8rem;
|
||||||
|
padding: 1.6rem;
|
||||||
|
margin: 1.6rem 1.6rem 0 1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Global style für copy-mode cursor */
|
||||||
|
.dest-mass-route-container.has-selection :deep(.dest-mass-route-row__cell--copyable:hover) {
|
||||||
|
cursor: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDEyOC41MSAxMzQuMDUiPjxkZWZzPjxzdHlsZT4uY3tmaWxsOm5vbmU7fS5jLC5ke3N0cm9rZTojMDEwMTAxO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utd2lkdGg6NXB4O30uZHtmaWxsOiNmZmY7fTwvc3R5bGU+PC9kZWZzPjxnIGlkPSJhIj48cGF0aCBjbGFzcz0iYyIgZD0ibTU0Ljg5LDExMi41MWgtMi4yNGMtMS4yNCwwLTIuMjQtMS0yLjI0LTIuMjR2LTIuMjQiLz48bGluZSBjbGFzcz0iYyIgeDE9IjcwLjU3IiB5MT0iNzYuNjciIHgyPSI2My44NSIgeTI9Ijc2LjY3Ii8+PGxpbmUgY2xhc3M9ImMiIHgxPSI3MC41NyIgeTE9IjExMi41MSIgeDI9IjY2LjA5IiB5Mj0iMTEyLjUxIi8+PGxpbmUgY2xhc3M9ImMiIHgxPSI4Ni4yNSIgeTE9Ijk5LjA3IiB4Mj0iODYuMjUiIHkyPSI5Mi4zNSIvPjxsaW5lIGNsYXNzPSJjIiB4MT0iNTAuNDEiIHkxPSI5Ni44MyIgeDI9IjUwLjQxIiB5Mj0iOTIuMzUiLz48cGF0aCBjbGFzcz0iYyIgZD0ibTgxLjc3LDExMi41MWgyLjI0YzEuMjQsMCwyLjI0LTEsMi4yNC0yLjI0di0yLjI0Ii8+PHBhdGggY2xhc3M9ImMiIGQ9Im04MS43Nyw3Ni42N2gyLjI0YzEuMjQsMCwyLjI0LDEsMi4yNCwyLjI0djIuMjQiLz48cGF0aCBjbGFzcz0iYyIgZD0ibTU0Ljg5LDc2LjY3aC0yLjI0Yy0xLjI0LDAtMi4yNCwxLTIuMjQsMi4yNHYyLjI0Ii8+PHBhdGggY2xhc3M9ImMiIGQ9Im04Ni4yNSw5OS4wN2gxMS4yYzEuMjQsMCwyLjI0LTEsMi4yNC0yLjI0di0zMS4zNmMwLTEuMjQtMS0yLjI0LTIuMjQtMi4yNGgtMzEuMzZjLTEuMjQsMC0yLjI0LDEtMi4yNCwyLjI0djExLjIiLz48L2c+PGcgaWQ9ImIiPjxwYXRoIGNsYXNzPSJkIiBkPSJtNDQuMDgsNDQuMDdsMzIuOTQtOS4yYzEuNjktLjUyLDIuNjQtMi4zMSwyLjEyLTQtLjMtLjk4LTEuMDUtMS43NS0yLjAxLTIuMDlMNi43MywyLjY3Yy0xLjY3LS41Ny0zLjQ5LjMzLTQuMDYsMi0uMjMuNjYtLjIzLDEuMzgsMCwyLjA1bDI2LjExLDcwLjRjLjU4LDEuNjcsMi40LDIuNTYsNC4wNywxLjk4Ljk3LS4zMywxLjcxLTEuMTEsMi4wMS0yLjA5bDkuMjItMzIuOTRaIi8+PC9nPjwvc3ZnPg==") 12 12, pointer;
|
||||||
|
background-color: #f8fafc;
|
||||||
|
border-radius: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Global style für filter-mode cursor */
|
||||||
|
.dest-mass-route-container.add-all :deep(.dest-mass-route-row__cell--filterable:hover) {
|
||||||
|
cursor: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDEyOC41MSAxMzQuMDUiPjxkZWZzPjxzdHlsZT4uY3tmaWxsOm5vbmU7fS5jLC5ke3N0cm9rZTojMDEwMTAxO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utd2lkdGg6NXB4O30uZHtmaWxsOiNmZmY7fTwvc3R5bGU+PC9kZWZzPjxnIGlkPSJhIj48bGluZSBjbGFzcz0iYyIgeDE9IjczLjAzIiB5MT0iNzUuNTUiIHgyPSI2Ni4zOCIgeTI9Ijc1LjU1Ii8+PGxpbmUgY2xhc3M9ImMiIHgxPSI2Ni4zOCIgeTE9IjExMi4xNiIgeDI9IjczLjAzIiB5Mj0iMTEyLjE2Ii8+PHBhdGggY2xhc3M9ImMiIGQ9Im04MS4zNSw3NS41NWg0Ljk5Yy45MiwwLDEuNjYuNzUsMS42NiwxLjY2djQuOTkiLz48bGluZSBjbGFzcz0iYyIgeDE9Ijg4LjAxIiB5MT0iOTcuMTgiIHgyPSI4OC4wMSIgeTI9IjkwLjUzIi8+PHBhdGggY2xhc3M9ImMiIGQ9Im04MS4zNSwxMTIuMTZoNC45OWMuOTIsMCwxLjY2LS43NSwxLjY2LTEuNjZ2LTQuOTkiLz48bGluZSBjbGFzcz0iYyIgeDE9IjUxLjQiIHkxPSI5MC41MyIgeDI9IjUxLjQiIHkyPSI5Ny4xOCIvPjxwYXRoIGNsYXNzPSJjIiBkPSJtNTguMDUsMTEyLjE2aC00Ljk5Yy0uOTIsMC0xLjY2LS43NS0xLjY2LTEuNjZ2LTQuOTkiLz48cGF0aCBjbGFzcz0iYyIgZD0ibTU4LjA1LDc1LjU1aC00Ljk5Yy0uOTIsMC0xLjY2Ljc1LTEuNjYsMS42NnY0Ljk5Ii8+PC9nPjxnIGlkPSJiIj48cGF0aCBjbGFzcz0iZCIgZD0ibTQ0LjA4LDQ0LjA3bDMyLjk0LTkuMmMxLjY5LS41MiwyLjY0LTIuMzEsMi4xMi00LS4zLS45OC0xLjA1LTEuNzUtMi4wMS0yLjA5TDYuNzMsMi42N2MtMS42Ny0uNTctMy40OS4zMy00LjA2LDItLjIzLjY2LS4yMywxLjM4LDAsMi4wNWwyNi4xMSw3MC40Yy41OCwxLjY3LDIuNCwyLjU2LDQuMDcsMS45OC45Ny0uMzMsMS43MS0xLjExLDIuMDEtMi4wOWw5LjIyLTMyLjk0WiIvPjwvZz48L3N2Zz4=") 12 12, pointer;
|
||||||
|
background-color: #f8fafc;
|
||||||
|
border-radius: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Global style für filter-mode cursor */
|
||||||
|
.dest-mass-route-container.apply-filter :deep(.dest-mass-route-row__cell--filterable:hover) {
|
||||||
|
cursor: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDEyOC41MSAxMzQuMDUiPjxkZWZzPjxzdHlsZT4uZHtzdHJva2U6IzAwMDt9LmQsLmV7ZmlsbDpub25lO30uZCwuZSwuZntzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLXdpZHRoOjVweDt9LmUsLmZ7c3Ryb2tlOiMwMTAxMDE7fS5me2ZpbGw6I2ZmZjt9PC9zdHlsZT48L2RlZnM+PGcgaWQ9ImEiPjxsaW5lIGNsYXNzPSJlIiB4MT0iNzMuMDMiIHkxPSI3NS41NSIgeDI9IjY2LjM4IiB5Mj0iNzUuNTUiLz48bGluZSBjbGFzcz0iZSIgeDE9IjY2LjM4IiB5MT0iMTEyLjE2IiB4Mj0iNzMuMDMiIHkyPSIxMTIuMTYiLz48cGF0aCBjbGFzcz0iZSIgZD0ibTgxLjM1LDc1LjU1aDQuOTljLjkyLDAsMS42Ni43NSwxLjY2LDEuNjZ2NC45OSIvPjxsaW5lIGNsYXNzPSJlIiB4MT0iODguMDEiIHkxPSI5Ny4xOCIgeDI9Ijg4LjAxIiB5Mj0iOTAuNTMiLz48cGF0aCBjbGFzcz0iZSIgZD0ibTgxLjM1LDExMi4xNmg0Ljk5Yy45MiwwLDEuNjYtLjc1LDEuNjYtMS42NnYtNC45OSIvPjxsaW5lIGNsYXNzPSJlIiB4MT0iNTEuNCIgeTE9IjkwLjUzIiB4Mj0iNTEuNCIgeTI9Ijk3LjE4Ii8+PHBhdGggY2xhc3M9ImUiIGQ9Im01OC4wNSwxMTIuMTZoLTQuOTljLS45MiwwLTEuNjYtLjc1LTEuNjYtMS42NnYtNC45OSIvPjxwYXRoIGNsYXNzPSJlIiBkPSJtNTguMDUsNzUuNTVoLTQuOTljLS45MiwwLTEuNjYuNzUtMS42NiwxLjY2djQuOTkiLz48L2c+PGcgaWQ9ImIiPjxwYXRoIGNsYXNzPSJmIiBkPSJtNDQuMDgsNDQuMDdsMzIuOTQtOS4yYzEuNjktLjUyLDIuNjQtMi4zMSwyLjEyLTQtLjMtLjk4LTEuMDUtMS43NS0yLjAxLTIuMDlMNi43MywyLjY3Yy0xLjY3LS41Ny0zLjQ5LjMzLTQuMDYsMi0uMjMuNjYtLjIzLDEuMzgsMCwyLjA1bDI2LjExLDcwLjRjLjU4LDEuNjcsMi40LDIuNTYsNC4wNywxLjk4Ljk3LS4zMywxLjcxLTEuMTEsMi4wMS0yLjA5bDkuMjItMzIuOTRaIi8+PC9nPjxnIGlkPSJjIj48bGluZSBjbGFzcz0iZCIgeDE9Ijk5LjM4IiB5MT0iOTQuMTkiIHgyPSIxMjYuMDEiIHkyPSI5NC4xOSIvPjxsaW5lIGNsYXNzPSJkIiB4MT0iMTEyLjY5IiB5MT0iODAuODciIHgyPSIxMTIuNjkiIHkyPSIxMDcuNTEiLz48L2c+PC9zdmc+") 12 12, pointer;
|
||||||
|
background-color: #f8fafc;
|
||||||
|
border-radius: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-container.disabled {
|
||||||
|
background-color: #f3f4f6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
border-color: #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-container.disabled input {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.text-container:hover:not(.disabled) {
|
||||||
|
background: #EEF4FF;
|
||||||
|
border: 0.2rem solid #8DB3FE;
|
||||||
|
transform: scale(1.01);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.input-field {
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
background: none;
|
||||||
|
resize: none;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
color: #002F54;
|
||||||
|
|
||||||
|
max-width: 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: white;
|
||||||
|
border-radius: 0.4rem;
|
||||||
|
padding: 0.6rem 1.2rem;
|
||||||
|
/* box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);*/
|
||||||
|
border: 0.2rem solid #E3EDFF;
|
||||||
|
transition: all 0.1s ease;
|
||||||
|
flex: 1 0 auto;
|
||||||
|
|
||||||
|
max-width: 8rem;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.dest-mass-route-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dest-mass-route-table-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dest-mass-route-table-header-wrapper {
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
/* Scrollbar verstecken aber Funktionalität behalten */
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
-ms-overflow-style: none; /* IE/Edge */
|
||||||
|
}
|
||||||
|
|
||||||
|
.dest-mass-route-table-header-wrapper::-webkit-scrollbar {
|
||||||
|
display: none; /* Chrome/Safari/Opera */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.dest-mass-route-table {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: auto;
|
||||||
|
min-height: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding-bottom: 2.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dest-mass-route-table-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 1.6rem;
|
||||||
|
padding: 1.6rem 2.4rem;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-bottom: 1px solid rgba(107, 134, 156, 0.2);
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
color: #6B869C;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08rem;
|
||||||
|
|
||||||
|
min-width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dest-mass-route-table-header-supplier {
|
||||||
|
width: 24rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.dest-mass-route-table-header-dest {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
width: 35rem;
|
||||||
|
gap: 0.8rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -0,0 +1,229 @@
|
||||||
|
<template>
|
||||||
|
<div class="dest-mass-route-cell">
|
||||||
|
<div class="dest-mass-route-dropdown">
|
||||||
|
<route-dropdown
|
||||||
|
placeholder="No route selected"
|
||||||
|
empty-text="No routes"
|
||||||
|
:disabled="destination.disabled"
|
||||||
|
:show-d2d-warn="showD2DWarn"
|
||||||
|
v-model:model-value="this.selectedRoute"
|
||||||
|
:options="destination.routes"
|
||||||
|
display-key="routeDisplayString"
|
||||||
|
value-key="routeCompareString"
|
||||||
|
@update:modelValue="updateSelectedRoute"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<icon-button :disabled="destination.disabled || this.selectedRoute !== 'D2D_ROUTING'" icon="pencilSimple" @click="openD2DModal">icon</icon-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<modal :state="modalState" @close="modalState = false">
|
||||||
|
<div class="destination-route-modal">
|
||||||
|
<div>
|
||||||
|
<div>D2D Rate [EUR]</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-container">
|
||||||
|
<input :value="this.rateD2d" @blur="validateRateD2d" class="input-field" ref="rate" @keydown.enter="handleEnter('rate', $event)"
|
||||||
|
autocomplete="off"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div> Lead time [days]</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-container">
|
||||||
|
<input :value="this.leadTimeD2d" @blur="validateLeadTimeD2d" class="input-field" ref="leadTime" @keydown.enter="handleEnter('leadTime', $event)"
|
||||||
|
autocomplete="off"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div></div>
|
||||||
|
<div class="destination-route-modal-footer">
|
||||||
|
<basic-button :show-icon="false" @click="applyD2D">OK</basic-button>
|
||||||
|
<basic-button variant="secondary" :show-icon="false" @click="dismissD2D">Cancel</basic-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import RouteDropdown from "@/components/UI/RouteDropdown.vue";
|
||||||
|
import IconButton from "@/components/UI/IconButton.vue";
|
||||||
|
import Modal from "@/components/UI/Modal.vue";
|
||||||
|
import DestinationRoute from "@/components/layout/edit/destination/DestinationRoute.vue";
|
||||||
|
import BasicButton from "@/components/UI/BasicButton.vue";
|
||||||
|
import {parseNumberFromString} from "@/common.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "DestinationMassRouteCell",
|
||||||
|
components: {BasicButton, DestinationRoute, Modal, IconButton, RouteDropdown},
|
||||||
|
props: {
|
||||||
|
destination: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
selectedRoute: null,
|
||||||
|
modalState: false,
|
||||||
|
rateD2d: null,
|
||||||
|
leadTimeD2d: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
showD2DWarn() {
|
||||||
|
return (this.destination.rateD2d === null || this.destination.leadTimeD2d === null || this.destination.rateD2d === 0 || this.destination.leadTimeD2d === 0);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if(this.destination.isD2d) {
|
||||||
|
this.selectedRoute = 'D2D_ROUTING'
|
||||||
|
} else {
|
||||||
|
this.selectedRoute = this.destination.selectedRoute;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleEnter(currentRef, event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
// Define the navigation order
|
||||||
|
const inputOrder = ['rate', 'leadTime'];
|
||||||
|
|
||||||
|
const currentIndex = inputOrder.indexOf(currentRef);
|
||||||
|
|
||||||
|
if(currentIndex >= inputOrder.length - 1) {
|
||||||
|
this.validateLeadTimeD2d(event);
|
||||||
|
this.applyD2D();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentIndex !== -1 && currentIndex < inputOrder.length - 1) {
|
||||||
|
const nextRef = inputOrder[currentIndex + 1];
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.$refs[nextRef]) {
|
||||||
|
this.$refs[nextRef].focus();
|
||||||
|
this.$refs[nextRef].select();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateSelectedRoute(route) {
|
||||||
|
if(route === 'D2D_ROUTING') {
|
||||||
|
this.destination.selectedRoute = null;
|
||||||
|
this.destination.isD2d = true;
|
||||||
|
} else {
|
||||||
|
this.destination.selectedRoute = route;
|
||||||
|
this.destination.isD2d = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
applyD2D() {
|
||||||
|
this.destination.rateD2d = this.rateD2d;
|
||||||
|
this.destination.leadTimeD2d = this.leadTimeD2d;
|
||||||
|
this.dismissD2D()
|
||||||
|
},
|
||||||
|
dismissD2D() {
|
||||||
|
this.modalState = false;
|
||||||
|
},
|
||||||
|
openD2DModal() {
|
||||||
|
this.rateD2d = this.destination.rateD2d;
|
||||||
|
this.leadTimeD2d = this.destination.leadTimeD2d;
|
||||||
|
this.modalState = true;
|
||||||
|
},
|
||||||
|
validateRateD2d(event) {
|
||||||
|
|
||||||
|
const value = parseNumberFromString(event.target.value, 2);
|
||||||
|
const validatedValue = Math.max(0, value);
|
||||||
|
const stringified = validatedValue.toFixed(2);
|
||||||
|
|
||||||
|
this.rateD2d = validatedValue === 0 ? null : validatedValue;
|
||||||
|
event.target.value = stringified;
|
||||||
|
},
|
||||||
|
validateLeadTimeD2d(event) {
|
||||||
|
|
||||||
|
const value = parseNumberFromString(event.target.value, 0);
|
||||||
|
const validatedValue = Math.max(0, value);
|
||||||
|
const stringified = validatedValue.toFixed();
|
||||||
|
|
||||||
|
this.leadTimeD2d = validatedValue === 0 ? null : validatedValue;
|
||||||
|
event.target.value = stringified;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
.destination-route-modal {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
gap: 1.6rem;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
font-weight: 400;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.destination-route-modal-footer {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.8rem;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dest-mass-route-cell {
|
||||||
|
width: 35rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 400;
|
||||||
|
gap: 0.8rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dest-mass-route-dropdown {
|
||||||
|
width: 30rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
font-weight: 400;
|
||||||
|
gap: 0.8rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-field {
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
background: none;
|
||||||
|
resize: none;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
color: #002F54;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: white;
|
||||||
|
border-radius: 0.4rem;
|
||||||
|
padding: 0.6rem 1.2rem;
|
||||||
|
border: 0.2rem solid #E3EDFF;
|
||||||
|
transition: all 0.1s ease;
|
||||||
|
flex: 1 1 fit-content(80rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-container:hover {
|
||||||
|
background: #EEF4FF;
|
||||||
|
border: 0.2rem solid #8DB3FE;
|
||||||
|
transform: scale(1.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
<template>
|
||||||
|
<div class="dest-mass-route-row-container">
|
||||||
|
<div class="dest-mass-route-row-supplier">
|
||||||
|
<flag :iso="row.supplier.country.iso_code"/>
|
||||||
|
{{ row.supplier.name }}
|
||||||
|
</div>
|
||||||
|
<destination-mass-route-cell :destination="dest" v-for="dest in row.destinations" class="dest-mass-route-row-dest"
|
||||||
|
:key="dest.id">
|
||||||
|
</destination-mass-route-cell>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import Checkbox from "@/components/UI/Checkbox.vue";
|
||||||
|
import Flag from "@/components/UI/Flag.vue";
|
||||||
|
import RouteDropdown from "@/components/UI/RouteDropdown.vue";
|
||||||
|
import DestinationMassRouteCell from "@/components/layout/edit/destination/mass/DestinationMassRouteCell.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "DestinationMassRouteRow",
|
||||||
|
components: {DestinationMassRouteCell, RouteDropdown, Flag, Checkbox},
|
||||||
|
props: {
|
||||||
|
row: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
routes() {
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleDropdown() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
|
||||||
|
.dest-mass-route-row-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 1.6rem;
|
||||||
|
padding: 1.2rem 2.4rem;
|
||||||
|
justify-content: flex-start;
|
||||||
|
border-bottom: 0.16rem solid #f3f4f6;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
min-width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dest-mass-route-row-container:hover {
|
||||||
|
background-color: rgba(107, 134, 156, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.dest-mass-route-row-supplier {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 0.8rem;
|
||||||
|
width: 24rem;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #6b7280;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -263,7 +263,7 @@ export default {
|
||||||
return this.modalType ? COMPONENT_TYPES[this.modalType] : null;
|
return this.modalType ? COMPONENT_TYPES[this.modalType] : null;
|
||||||
},
|
},
|
||||||
showProcessingModal() {
|
showProcessingModal() {
|
||||||
return this.premiseEditStore.showProcessingModal || this.destinationEditStore.showProcessingModal ;
|
return this.premiseEditStore.showProcessingModal || this.destinationEditStore.showProcessingModal;
|
||||||
},
|
},
|
||||||
shownProcessingMessage() {
|
shownProcessingMessage() {
|
||||||
return this.processingMessage;
|
return this.processingMessage;
|
||||||
|
|
@ -425,7 +425,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((type === 'amount' || type === 'routes')) {
|
if ((type === 'amount' || type === 'routes')) {
|
||||||
if(dataSource !== -1)
|
if (dataSource !== -1)
|
||||||
ids = [dataSource];
|
ids = [dataSource];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -439,13 +439,18 @@ export default {
|
||||||
},
|
},
|
||||||
async closeEditModalAction(action) {
|
async closeEditModalAction(action) {
|
||||||
|
|
||||||
if (this.modalType === "destinations") {
|
if (this.modalType === 'amount' || this.modalType === 'routes' || this.modalType === "destinations") {
|
||||||
|
|
||||||
if (action === 'accept') {
|
if (action === 'accept') {
|
||||||
const destMatrix = this.$refs.modalComponent?.destMatrix;
|
|
||||||
|
|
||||||
if (destMatrix) {
|
if (this.modalType === "destinations") {
|
||||||
await this.destinationEditStore.massSetDestinations(destMatrix);
|
const setMatrix = this.$refs.modalComponent?.destMatrix;
|
||||||
|
|
||||||
|
if (setMatrix) {
|
||||||
|
await this.destinationEditStore.massSetDestinations(setMatrix);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await this.destinationEditStore.massUpdateDestinations();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -503,9 +508,10 @@ export default {
|
||||||
stackable: true
|
stackable: true
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type === 'amount' || type === 'routes' || type === 'destinations')
|
if (type === 'amount' || type === 'routes' || type === 'destinations') {
|
||||||
|
this.modalTitle = "Edit destinations";
|
||||||
this.modalProps = {};
|
this.modalProps = {};
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
const premise = this.premiseEditStore.getById(id);
|
const premise = this.premiseEditStore.getById(id);
|
||||||
|
|
@ -545,10 +551,8 @@ export default {
|
||||||
this.modalTitle = "Edit destinations";
|
this.modalTitle = "Edit destinations";
|
||||||
this.modalProps = {type: type};
|
this.modalProps = {type: type};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
,
|
|
||||||
|
|
||||||
/* Animation hooks */
|
/* Animation hooks */
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,10 @@ export const useDestinationEditStore = defineStore('destinationEdit', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
destinations: null,
|
destinations: null,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
initialized: false,
|
||||||
|
handlingCostMatrix: null,
|
||||||
|
quantityMatrix: null,
|
||||||
|
routeMatrix: null
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
checkDestinationAssignment(state) {
|
checkDestinationAssignment(state) {
|
||||||
|
|
@ -45,9 +49,27 @@ export const useDestinationEditStore = defineStore('destinationEdit', {
|
||||||
},
|
},
|
||||||
showProcessingModal(state) {
|
showProcessingModal(state) {
|
||||||
return state.loading;
|
return state.loading;
|
||||||
|
},
|
||||||
|
getHandlingCostMatrix(state) {
|
||||||
|
return state.handlingCostMatrix;
|
||||||
|
},
|
||||||
|
getQuantityMatrix(state) {
|
||||||
|
return state.quantityMatrix;
|
||||||
|
},
|
||||||
|
getRouteMatrix(state) {
|
||||||
|
return state.routeMatrix;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
setHandlingCostMatrix(handlingCostMatrix) {
|
||||||
|
this.handlingCostMatrix = handlingCostMatrix;
|
||||||
|
},
|
||||||
|
setQuantityMatrix(quantityMatrix) {
|
||||||
|
this.quantityMatrix = quantityMatrix;
|
||||||
|
},
|
||||||
|
setRouteMatrix(routeMatrix) {
|
||||||
|
this.routeMatrix = routeMatrix;
|
||||||
|
},
|
||||||
setupDestinations(premisses) {
|
setupDestinations(premisses) {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
|
||||||
|
|
@ -55,6 +77,7 @@ export const useDestinationEditStore = defineStore('destinationEdit', {
|
||||||
premisses.forEach(p => temp.set(p.id, p.destinations));
|
premisses.forEach(p => temp.set(p.id, p.destinations));
|
||||||
this.destinations = temp;
|
this.destinations = temp;
|
||||||
|
|
||||||
|
this.initialized = true;
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
},
|
},
|
||||||
async massSetDestinations(updateMatrix) {
|
async massSetDestinations(updateMatrix) {
|
||||||
|
|
@ -85,11 +108,51 @@ export const useDestinationEditStore = defineStore('destinationEdit', {
|
||||||
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
},
|
},
|
||||||
async massUpdateDestinations(updateMatrix) {
|
async massUpdateDestinations() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
|
||||||
|
this.updateQuantity();
|
||||||
|
this.updateHandlingCosts();
|
||||||
|
|
||||||
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
},
|
},
|
||||||
|
updateHandlingCosts() {
|
||||||
|
this.handlingCostMatrix.forEach(row => {
|
||||||
|
const destinations = this.destinations.get(row.id);
|
||||||
|
|
||||||
|
if ((destinations ?? null) !== null) {
|
||||||
|
|
||||||
|
const destination = destinations.find(dest => dest.id === row.destinationId);
|
||||||
|
|
||||||
|
if((destination ?? null) !== null) {
|
||||||
|
destination.disposal_costs = row.disposal_costs;
|
||||||
|
destination.repackaging_costs = row.repackaging_costs;
|
||||||
|
destination.handling_costs = row.handling_costs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
updateQuantity() {
|
||||||
|
this.quantityMatrix.forEach(row => {
|
||||||
|
const destinations = this.destinations.get(row.id);
|
||||||
|
|
||||||
|
|
||||||
|
if ((destinations ?? null) !== null) {
|
||||||
|
row.destinations
|
||||||
|
.filter(newDest => newDest.id !== null)
|
||||||
|
.forEach(newDest => {
|
||||||
|
|
||||||
|
const found = destinations.find(dest => dest.id === newDest.id);
|
||||||
|
if (found)
|
||||||
|
found.annual_amount = newDest.annual_amount;
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Loading…
Add table
Reference in a new issue