Files
yourpart3/frontend/src/components/falukant/DirectorInfo.vue
Torsten Schulz (local) 98dea7dd39 Implement empty transport feature in DirectorInfo component
- 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.
2025-12-04 14:48:55 +01:00

536 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>