- Added functionality to allow directors to initiate empty transports without products, enhancing logistics management. - Introduced a new transport form in the DirectorInfo component, enabling selection of vehicle types and target branches. - Updated the i18n localization files to include new translations for the empty transport feature. - Enhanced the BranchView to pass vehicle and branch data to the DirectorInfo component, ensuring proper functionality. - This update aims to improve user experience and streamline transport operations within the application.
536 lines
15 KiB
Vue
536 lines
15 KiB
Vue
<template>
|
||
<div class="director-info">
|
||
<div v-if="!director || director === null">
|
||
<button @click="openNewDirectorDialog">
|
||
{{ $t('falukant.branch.director.actions.new') }}
|
||
</button>
|
||
</div>
|
||
|
||
<div v-else class="director-info-container">
|
||
<!-- Linke Seite: Stammdaten & Wissen (aus /falukant/directors) -->
|
||
<div class="director-main">
|
||
<h3 class="director-name">
|
||
{{ $t('falukant.titles.' + director.character.gender + '.' + director.character.nobleTitle.labelTr) }}
|
||
{{ director.character.definedFirstName.name }} {{ director.character.definedLastName.name }}
|
||
</h3>
|
||
<p class="director-meta">
|
||
{{ $t('falukant.director.age') }}:
|
||
{{ director.character.age }}
|
||
<span v-if="director.region"> — {{ director.region }}</span>
|
||
</p>
|
||
|
||
<div
|
||
v-if="director.character.knowledges && director.character.knowledges.length"
|
||
class="knowledge-panel"
|
||
>
|
||
<h4>{{ $t('falukant.director.knowledge.title') }}</h4>
|
||
<div class="table-container">
|
||
<table class="knowledge-table">
|
||
<thead>
|
||
<tr>
|
||
<th>{{ $t('falukant.director.product') }}</th>
|
||
<th>{{ $t('falukant.director.knowledge.knowledge') }}</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr
|
||
v-for="item in director.character.knowledges"
|
||
:key="item.productId"
|
||
>
|
||
<td>{{ $t(`falukant.product.${item.productType.labelTr}`) }}</td>
|
||
<td>{{ item.knowledge }} %</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Rechte Seite: Aktionen, Einkommen, Rechte -->
|
||
<div class="director-actions">
|
||
<div class="field">
|
||
<label>
|
||
{{ $t('falukant.branch.director.satisfaction') }}:
|
||
<span>{{ director.satisfaction }} %</span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<label>
|
||
{{ $t('falukant.branch.director.income') }}:
|
||
<input type="number" v-model.number="editIncome" />
|
||
</label>
|
||
<span
|
||
v-if="director.wishedIncome != null"
|
||
class="link"
|
||
@click="setWishedIncome"
|
||
>
|
||
({{ $t('falukant.director.wishedIncome') }}:
|
||
{{ director.wishedIncome }})
|
||
</span>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<button @click="updateDirector">
|
||
{{ $t('falukant.director.updateButton') }}
|
||
</button>
|
||
</div>
|
||
|
||
<div class="field toggles">
|
||
<label>
|
||
<input
|
||
type="checkbox"
|
||
v-model="director.mayProduce"
|
||
@change="saveSetting('mayProduce', director.mayProduce)"
|
||
/>
|
||
{{ $t('falukant.branch.director.produce') }}
|
||
</label>
|
||
<label>
|
||
<input
|
||
type="checkbox"
|
||
v-model="director.maySell"
|
||
@change="saveSetting('maySell', director.maySell)"
|
||
/>
|
||
{{ $t('falukant.branch.director.sell') }}
|
||
</label>
|
||
<label>
|
||
<input
|
||
type="checkbox"
|
||
v-model="director.mayStartTransport"
|
||
@change="saveSetting('mayStartTransport', director.mayStartTransport)"
|
||
/>
|
||
{{ $t('falukant.branch.director.starttransport') }}
|
||
</label>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<button @click="fireDirector">
|
||
{{ $t('falukant.branch.director.fire') }}
|
||
</button>
|
||
</div>
|
||
|
||
<div class="field">
|
||
<button @click="teachDirector">
|
||
{{ $t('falukant.branch.director.teach') }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Transport ohne Produkte (nur wenn Direktor vorhanden und mayStartTransport aktiviert) -->
|
||
<div v-if="director && director.mayStartTransport" class="director-transport-section">
|
||
<h4>{{ $t('falukant.branch.director.emptyTransport.title') }}</h4>
|
||
<p class="transport-description">
|
||
{{ $t('falukant.branch.director.emptyTransport.description') }}
|
||
</p>
|
||
<div class="transport-form">
|
||
<label>
|
||
{{ $t('falukant.branch.director.emptyTransport.vehicleType') }}
|
||
<select v-model.number="emptyTransportForm.vehicleTypeId" @change="loadEmptyTransportRoute">
|
||
<option :value="null" disabled>{{ $t('falukant.branch.director.emptyTransport.selectVehicle') }}</option>
|
||
<option v-for="vt in vehicleTypeOptions()" :key="vt.id" :value="vt.id">
|
||
{{ $t(`falukant.branch.vehicles.${vt.tr}`) }} ({{ vt.count }} × {{ vt.capacity }})
|
||
</option>
|
||
</select>
|
||
</label>
|
||
|
||
<label>
|
||
{{ $t('falukant.branch.director.emptyTransport.targetBranch') }}
|
||
<select v-model.number="emptyTransportForm.targetBranchId" @change="loadEmptyTransportRoute">
|
||
<option :value="null" disabled>{{ $t('falukant.branch.director.emptyTransport.selectTarget') }}</option>
|
||
<option v-for="tb in targetBranchOptions" :key="tb.id" :value="tb.id">
|
||
{{ tb.label }}
|
||
</option>
|
||
</select>
|
||
</label>
|
||
|
||
<div v-if="emptyTransportForm.costLabel" class="transport-cost">
|
||
{{ $t('falukant.branch.director.emptyTransport.cost', { cost: emptyTransportForm.costLabel }) }}
|
||
</div>
|
||
|
||
<div class="transport-route" v-if="emptyTransportForm.durationLabel">
|
||
<div>
|
||
{{ $t('falukant.branch.director.emptyTransport.duration', { duration: emptyTransportForm.durationLabel }) }}
|
||
</div>
|
||
<div>
|
||
{{ $t('falukant.branch.director.emptyTransport.arrival', { datetime: emptyTransportForm.etaLabel }) }}
|
||
</div>
|
||
<div v-if="emptyTransportForm.routeNames && emptyTransportForm.routeNames.length">
|
||
{{ $t('falukant.branch.director.emptyTransport.route') }}:
|
||
{{ emptyTransportForm.routeNames.join(' → ') }}
|
||
</div>
|
||
</div>
|
||
|
||
<button
|
||
@click="createEmptyTransport"
|
||
:disabled="!emptyTransportForm.vehicleTypeId || !emptyTransportForm.targetBranchId"
|
||
>
|
||
{{ $t('falukant.branch.director.emptyTransport.create') }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<NewDirectorDialog ref="newDirectorDialog" />
|
||
</template>
|
||
|
||
<script>
|
||
import apiClient from '@/utils/axios.js';
|
||
import NewDirectorDialog from '@/dialogues/falukant/NewDirectorDialog.vue';
|
||
|
||
export default {
|
||
name: "DirectorInfo",
|
||
props: {
|
||
branchId: { type: Number, required: true },
|
||
vehicles: { type: Array, default: () => [] },
|
||
branches: { type: Array, default: () => [] },
|
||
},
|
||
components: {
|
||
NewDirectorDialog
|
||
},
|
||
data() {
|
||
return {
|
||
director: null,
|
||
showNewDirectorDialog: false,
|
||
editIncome: null,
|
||
emptyTransportForm: {
|
||
vehicleTypeId: null,
|
||
targetBranchId: null,
|
||
distance: null,
|
||
durationHours: null,
|
||
eta: null,
|
||
durationLabel: '',
|
||
etaLabel: '',
|
||
routeNames: [],
|
||
cost: 0.1,
|
||
costLabel: '',
|
||
},
|
||
};
|
||
},
|
||
async mounted() {
|
||
await this.loadDirector();
|
||
},
|
||
methods: {
|
||
async refresh() {
|
||
await this.loadDirector();
|
||
},
|
||
|
||
async loadDirector() {
|
||
try {
|
||
const response = await apiClient.get(`/api/falukant/director/${this.branchId}`);
|
||
const data = response.data;
|
||
if (
|
||
!data ||
|
||
(Array.isArray(data) && data.length === 0) ||
|
||
typeof data.director === 'undefined' ||
|
||
data.director === null
|
||
) {
|
||
this.director = null;
|
||
this.editIncome = null;
|
||
} else {
|
||
this.director = data.director;
|
||
this.editIncome = this.director.income;
|
||
}
|
||
} catch (error) {
|
||
console.error('Error loading director:', error);
|
||
this.director = null;
|
||
this.editIncome = null;
|
||
}
|
||
},
|
||
|
||
async saveSetting(settingKey, value) {
|
||
if (!this.director) return;
|
||
try {
|
||
await apiClient.post(`/api/falukant/director/settings`, {
|
||
branchId: this.branchId,
|
||
directorId: this.director.id,
|
||
settingKey,
|
||
value,
|
||
});
|
||
} catch (error) {
|
||
console.error(`Error saving setting ${settingKey}:`, error);
|
||
}
|
||
},
|
||
|
||
openNewDirectorDialog() {
|
||
console.log('openNewDirectorDialog');
|
||
this.$refs.newDirectorDialog.open(this.branchId);
|
||
},
|
||
|
||
async updateDirector() {
|
||
if (!this.director || this.editIncome == null) return;
|
||
try {
|
||
await apiClient.post(`/api/falukant/directors`, {
|
||
directorId: this.director.id,
|
||
income: this.editIncome,
|
||
});
|
||
await this.loadDirector();
|
||
} catch (error) {
|
||
console.error('Error updating director:', error);
|
||
}
|
||
},
|
||
|
||
setWishedIncome() {
|
||
if (!this.director || this.director.wishedIncome == null) return;
|
||
this.editIncome = this.director.wishedIncome;
|
||
},
|
||
|
||
fireDirector() {
|
||
alert(this.$t('falukant.branch.director.fireAlert'));
|
||
},
|
||
|
||
teachDirector() {
|
||
alert(this.$t('falukant.branch.director.teachAlert'));
|
||
},
|
||
|
||
vehicleTypeOptions() {
|
||
const groups = {};
|
||
for (const v of this.vehicles || []) {
|
||
if (v.status !== 'available' || !v.type || !v.type.id) continue;
|
||
const id = v.type.id;
|
||
if (!groups[id]) {
|
||
groups[id] = {
|
||
id,
|
||
tr: v.type.tr,
|
||
capacity: v.type.capacity,
|
||
speed: v.type.speed,
|
||
count: 0,
|
||
};
|
||
}
|
||
groups[id].count += 1;
|
||
}
|
||
return Object.values(groups);
|
||
},
|
||
|
||
targetBranchOptions() {
|
||
return (this.branches || [])
|
||
.filter(b => ['store', 'fullstack'].includes(b.branchTypeLabelTr))
|
||
.filter(b => b.id !== this.branchId)
|
||
.map(b => ({
|
||
id: b.id,
|
||
label: `${b.cityName} – ${b.type}`,
|
||
}));
|
||
},
|
||
|
||
async loadEmptyTransportRoute() {
|
||
this.emptyTransportForm.distance = null;
|
||
this.emptyTransportForm.durationHours = null;
|
||
this.emptyTransportForm.eta = null;
|
||
this.emptyTransportForm.durationLabel = '';
|
||
this.emptyTransportForm.etaLabel = '';
|
||
this.emptyTransportForm.routeNames = [];
|
||
|
||
const vType = this.vehicleTypeOptions().find(v => v.id === this.emptyTransportForm.vehicleTypeId);
|
||
const targetBranch = (this.branches || []).find(b => b.id === this.emptyTransportForm.targetBranchId);
|
||
const sourceBranch = (this.branches || []).find(b => b.id === this.branchId);
|
||
|
||
if (!vType || !targetBranch || !sourceBranch) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const { data } = await apiClient.get('/api/falukant/transports/route', {
|
||
params: {
|
||
sourceRegionId: sourceBranch.regionId,
|
||
targetRegionId: targetBranch.regionId,
|
||
vehicleTypeId: vType.id,
|
||
},
|
||
});
|
||
if (data && data.totalDistance != null) {
|
||
const distance = data.totalDistance;
|
||
const speed = vType.speed || 1;
|
||
const hours = distance / speed;
|
||
|
||
this.emptyTransportForm.distance = distance;
|
||
this.emptyTransportForm.durationHours = hours;
|
||
|
||
const now = new Date();
|
||
const etaMs = now.getTime() + hours * 60 * 60 * 1000;
|
||
const etaDate = new Date(etaMs);
|
||
|
||
const fullHours = Math.floor(hours);
|
||
const minutes = Math.round((hours - fullHours) * 60);
|
||
const parts = [];
|
||
if (fullHours > 0) parts.push(`${fullHours} h`);
|
||
if (minutes > 0) parts.push(`${minutes} min`);
|
||
this.emptyTransportForm.durationLabel = parts.length ? parts.join(' ') : '0 min';
|
||
this.emptyTransportForm.etaLabel = etaDate.toLocaleString();
|
||
|
||
this.emptyTransportForm.routeNames = (data.regions || []).map(r => r.name);
|
||
}
|
||
// Kosten für leeren Transport: 0.1
|
||
this.emptyTransportForm.cost = 0.1;
|
||
this.emptyTransportForm.costLabel = this.formatMoney(0.1);
|
||
} catch (error) {
|
||
console.error('Error loading transport route:', error);
|
||
this.emptyTransportForm.distance = null;
|
||
this.emptyTransportForm.durationHours = null;
|
||
this.emptyTransportForm.eta = null;
|
||
this.emptyTransportForm.durationLabel = '';
|
||
this.emptyTransportForm.etaLabel = '';
|
||
this.emptyTransportForm.routeNames = [];
|
||
}
|
||
},
|
||
|
||
formatMoney(amount) {
|
||
if (amount == null) return '';
|
||
try {
|
||
return amount.toLocaleString(undefined, {
|
||
minimumFractionDigits: 1,
|
||
maximumFractionDigits: 1,
|
||
});
|
||
} catch (e) {
|
||
return String(amount);
|
||
}
|
||
},
|
||
|
||
async createEmptyTransport() {
|
||
if (!this.emptyTransportForm.vehicleTypeId || !this.emptyTransportForm.targetBranchId) {
|
||
return;
|
||
}
|
||
try {
|
||
await apiClient.post('/api/falukant/transports', {
|
||
branchId: this.branchId,
|
||
vehicleTypeId: this.emptyTransportForm.vehicleTypeId,
|
||
productId: null,
|
||
quantity: 0,
|
||
targetBranchId: this.emptyTransportForm.targetBranchId,
|
||
});
|
||
// Formular zurücksetzen
|
||
this.emptyTransportForm = {
|
||
vehicleTypeId: null,
|
||
targetBranchId: null,
|
||
distance: null,
|
||
durationHours: null,
|
||
eta: null,
|
||
durationLabel: '',
|
||
etaLabel: '',
|
||
routeNames: [],
|
||
cost: 0.1,
|
||
costLabel: '',
|
||
};
|
||
alert(this.$t('falukant.branch.director.emptyTransport.success'));
|
||
this.$emit('transportCreated');
|
||
} catch (error) {
|
||
console.error('Error creating empty transport:', error);
|
||
alert(this.$t('falukant.branch.director.emptyTransport.error'));
|
||
}
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.director-info {
|
||
border: 1px solid #ccc;
|
||
margin: 10px 0;
|
||
border-radius: 4px;
|
||
padding: 10px;
|
||
}
|
||
|
||
.director-info-container {
|
||
display: flex;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.director-main {
|
||
flex: 2;
|
||
}
|
||
|
||
.director-actions {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.director-name {
|
||
margin: 0 0 0.25rem 0;
|
||
}
|
||
|
||
.director-meta {
|
||
margin: 0 0 0.75rem 0;
|
||
font-style: italic;
|
||
}
|
||
|
||
.field {
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.field label {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.toggles label {
|
||
display: block;
|
||
}
|
||
|
||
.link {
|
||
color: #007bff;
|
||
cursor: pointer;
|
||
margin-left: 0.5rem;
|
||
}
|
||
|
||
.table-container {
|
||
max-height: 40vh;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.knowledge-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
}
|
||
|
||
.knowledge-table th,
|
||
.knowledge-table td {
|
||
border: 1px solid #ddd;
|
||
padding: 4px 6px;
|
||
}
|
||
|
||
.director-transport-section {
|
||
margin-top: 2rem;
|
||
padding-top: 1rem;
|
||
border-top: 1px solid #ddd;
|
||
}
|
||
|
||
.transport-description {
|
||
margin-bottom: 1rem;
|
||
color: #666;
|
||
font-style: italic;
|
||
}
|
||
|
||
.transport-form {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.75rem;
|
||
}
|
||
|
||
.transport-form label {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.25rem;
|
||
}
|
||
|
||
.transport-form select {
|
||
padding: 0.5rem;
|
||
border: 1px solid #ccc;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.transport-cost {
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
.transport-route {
|
||
padding: 0.75rem;
|
||
background-color: #f5f5f5;
|
||
border-radius: 4px;
|
||
font-size: 0.9em;
|
||
}
|
||
|
||
.transport-route > div {
|
||
margin-bottom: 0.25rem;
|
||
}
|
||
</style> |