lcc_tool/src/frontend/src/components/layout/config/Rates.vue
Jan 5d775a7dda Standardized date formatting across components by introducing buildDate utility:
- Replaced duplicate date-handling logic in multiple components (e.g., `CountryProperties.vue`, `Rates.vue`, `ErrorLog.vue`) with centralized `buildDate` method from `common.js`.
- Improved date consistency and readability by adopting `YYYY/MM/DD` format with optional time support.
2025-11-10 14:50:59 +01:00

280 lines
No EOL
8.5 KiB
Vue

<template>
<div class="container-rate-container">
<staged-rates ref="stagedRatesRef" @rates-update="reloadTable"></staged-rates>
<div class="container-rate-header">
<div class="container-rate-search-container">
<span class="period-select-caption">Rate type:</span>
<radio-option name="rateType" value="container" v-model="rateType">Container rates</radio-option>
<radio-option name="rateType" value="matrix" v-model="rateType">Kilometer rates</radio-option>
</div>
<div v-if="!loadingPeriods" class="period-select-container">
<span class="period-select-caption">Validity period:</span>
<dropdown :options="periods"
emptyText="No validity period available"
class="period-select"
placeholder="Select a validity period"
v-model="selectedPeriod"
></dropdown>
<tooltip position="left" text="Invalidate the selected validity period">
<icon-button icon="trash" @click="deletePeriod" :disabled="disableDeleteButton"></icon-button>
</tooltip>
<modal-dialog title="Do you really want to invalidate this validity period?"
dismiss-text="No"
accept-text="Yes"
:state="modalDialogDeleteState"
message="If you invalidate this property set, this will also invalidate all calculations done with this property set. This cannot be undone!"
@click="deleteModalClick"
>
</modal-dialog>
</div>
</div>
<table-view ref="tableViewRef" :data-source="fetch" :columns="selectedTypeColumns" :page="pagination.page"
:page-size="pageSize" :page-count="pagination.pageCount"
:total-count="pagination.totalCount"></table-view>
</div>
</template>
<script>
import Dropdown from "@/components/UI/Dropdown.vue";
import IconButton from "@/components/UI/IconButton.vue";
import Tooltip from "@/components/UI/Tooltip.vue";
import ModalDialog from "@/components/UI/ModalDialog.vue";
import {mapStores} from "pinia";
import {useValidityPeriodStore} from "@/store/validityPeriod.js";
import AutosuggestSearchbar from "@/components/UI/AutoSuggestSearchBar.vue";
import DataTable from "@/components/UI/DataTable.vue";
import TableView from "@/components/UI/TableView.vue";
import RadioOption from "@/components/UI/RadioOption.vue";
import {useMatrixRateStore} from "@/store/matrixRate.js";
import {useContainerRateStore} from "@/store/containerRate.js";
import StagedRates from "@/components/layout/config/StagedRates.vue";
import {buildDate} from "@/common.js";
export default {
name: "Rates",
props: {
isSelected: {
type: Boolean,
default: false
}
},
watch: {
async isSelected(newVal) {
if (newVal === true) {
await this.validityPeriodStore.loadPeriods();
await this.matrixRateStore.setQuery();
await this.containerRateStore.setQuery();
this.$refs.stagedRatesRef.checkChanges();
this.pagination = this.rateType === 'container' ? this.containerRateStore.getPagination : this.matrixRateStore.getPagination;
}
}
},
components: {
StagedRates,
RadioOption, TableView, DataTable, AutosuggestSearchbar, ModalDialog, Tooltip, IconButton, Dropdown
},
computed: {
...mapStores(useValidityPeriodStore, useMatrixRateStore, useContainerRateStore),
loadingPeriods() {
// return this.propertiesStore.isLoading;
return false;
},
isValidPeriodActive() {
const state = this.validityPeriodStore.getPeriodState(this.selectedPeriod);
return state === "VALID" || state === "DRAFT";
},
disableDeleteButton() {
const state = this.validityPeriodStore.getPeriodState(this.selectedPeriod);
return state === "VALID" || state === "INVALID" || state === "DRAFT";
},
rateType: {
get() {
return this.rateTypeValue;
},
set(value) {
if (value === "matrix") {
this.selectedTypeColumns = this.matrixColumns;
} else {
this.selectedTypeColumns = this.containerColumns;
}
this.rateTypeValue = value;
this.$refs.tableViewRef.reload('', 1);
}
},
selectedPeriod: {
get() {
return this.validityPeriodStore.getSelectedPeriod
},
async set(value) {
this.validityPeriodStore.setSelectedPeriod(value);
this.$refs.tableViewRef.reload('', 1);
}
},
periods() {
const periods = [];
const vp = this.validityPeriodStore.getPeriods;
if ((vp ?? null) === null) {
return null;
}
for (const p of vp) {
const value = (p.state === "DRAFT") ? "DRAFT" : (p.state === "VALID") ? "CURRENT" : `${this.buildDate(p.start_date)} - ${this.buildDate(p.end_date)} ${p.state === "INVALID" ? "(INVALID)" : ""}`;
const period = {id: p.id, value: value};
periods.push(period);
}
return periods;
}
},
data() {
return {
pageSize: 20,
pagination: {page: 1, pageCount: 1, totalCount: 1},
rateTypeValue: "container",
selectedTypeColumns: [],
matrixColumns: [
{key: 'source.iso_code', label: 'From Country (ISO code)'},
{key: 'destination.iso_code', label: 'To Country (ISO code)'},
{key: 'rate', align: 'right', label: 'Rate [EUR/km]'},
],
containerColumns: [
{
key: 'type', label: 'Type', align: 'center', iconResolver: (rawValue, _) => {
if (rawValue === "SEA") {
return "PhBoat";
} else if (rawValue === "RAIL") {
return "PhTrain"
} else if (rawValue === "ROAD" || rawValue === "POST_RUN") {
return "PhTruck"
}
}
},
{key: 'source.name', align: 'left', label: 'From node'},
{key: 'destination.name', align: 'left', label: 'To node'},
{key: 'rates.TEU', align: 'right', label: '20 ft. GP rate [EUR]'},
{key: 'rates.FEU', align: 'right', label: '40 ft. GP rate [EUR]'},
{key: 'rates.HC', align: 'right', label: '40 ft. HC rate [EUR]'},
{key: 'lead_time', align: 'right', label: 'Lead time [days]'},
],
modalDialogDeleteState: false,
selectedPeriodId: null
}
},
async created() {
this.selectedTypeColumns = this.containerColumns;
await this.validityPeriodStore.loadPeriods();
await this.matrixRateStore.setQuery();
await this.containerRateStore.setQuery();
this.pagination = this.rateType === 'container' ? this.containerRateStore.getPagination : this.matrixRateStore.getPagination;
},
methods: {
buildDate(date) {
return buildDate(date, false);
},
async fetch(query) {
query.periodId = this.selectedPeriod;
if (this.rateType === "container") {
await this.containerRateStore.setQuery(query);
this.pagination = this.containerRateStore.getPagination;
return this.containerRateStore.getRates;
} else if (this.rateType === "matrix") {
await this.matrixRateStore.setQuery(query);
this.pagination = this.matrixRateStore.getPagination;
return this.matrixRateStore.getRates;
}
return [];
},
deletePeriod() {
if (!this.disableDeleteButton) {
this.modalDialogDeleteState = true;
}
},
async reloadTable() {
await this.validityPeriodStore.loadPeriods();
await this.matrixRateStore.setQuery();
await this.containerRateStore.setQuery();
this.pagination = this.rateType === 'container' ? this.containerRateStore.getPagination : this.matrixRateStore.getPagination;
},
deleteModalClick() {
this.modalDialogDeleteState = false;
if (action === 'accept')
this.validPeriodStore.invalidate();
}
}
}
</script>
<style scoped>
.table-view {
margin-top: 1.6rem;
}
.container-rate-container {
display: flex;
flex-direction: column;
}
.container-rate-header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: flex-end;
gap: 3.2rem;
}
.container-rate-search-container {
display: flex;
justify-content: stretch;
align-items: center;
flex: 1;
margin-bottom: 1rem;
gap: 1.6rem;
font-size: 1.4rem;
}
.container-rate-search-searchbar {
flex: 1;
}
.period-select-container {
display: flex;
justify-content: flex-end;
align-items: center;
margin-bottom: 1rem;
gap: 1.6rem;
font-size: 1.4rem;
min-width: 50rem;
}
.period-select-caption {
font-weight: 500;
}
</style>