Files
yourpart3/frontend/src/views/admin/falukant/EditUserView.vue

693 lines
19 KiB
Vue

<template>
<div class="edit-user-view">
<h1>{{ $t('admin.falukant.edituser.title') }}</h1>
<!-- Benutzer-Suche -->
<div class="search-section">
<label class="form-field">{{ $t('admin.falukant.edituser.username') }} <input type="text" v-model="user.username" @keyup.enter="searchUser" /></label>
<label class="form-field">{{ $t('admin.falukant.edituser.characterName') }} <input type="text" v-model="user.characterName" @keyup.enter="searchUser" /></label>
<button @click="searchUser" :disabled="!canSearch">{{ $t('admin.falukant.edituser.search') }}</button>
</div>
<!-- Benutzer-Liste -->
<ul v-for="user in users" class="user-list">
<li @click="selectUser(user)">{{ user.username }} ({{ user.falukantUser[0].character.definedFirstName.name }} {{
user.falukantUser[0].character.definedLastName.name }})</li>
</ul>
<!-- Tab-System für ausgewählten Benutzer -->
<div v-if="editableUser" class="user-edit-section">
<h2>{{ $t('admin.falukant.edituser.user') }}: {{ editableUser.username }}</h2>
<h3>{{ $t('admin.falukant.edituser.characterName') }}: {{ editableUser.falukantData[0].character.definedFirstName.name }} {{
editableUser.falukantData[0].character.definedLastName.name }}</h3>
<SimpleTabs v-model="activeTab" :tabs="tabs" @change="onTabChange" />
<!-- Tab-Inhalt -->
<div class="tab-content">
<!-- Userdaten Tab -->
<div v-if="activeTab === 'userdata'" class="tab-pane">
<div class="edit-form">
<label>{{ $t('falukant.overview.metadata.money') }}: <input type="number" v-model="editableUser.falukantData[0].money" /></label>
<label>{{ $t('falukant.overview.metadata.age') }}: <input type="number" v-model="age" /></label>
<label>{{ $t('falukant.overview.metadata.nobleTitle') }}:
<select v-model="editableUser.falukantData[0].character.title_of_nobility">
<option v-for="title in titles" :value="title.id">{{ $t(`falukant.titles.male.${title.labelTr}`) }}</option>
</select>
</label>
<label>{{ $t('falukant.house.title') }}: <select v-model="editableUser.falukantData[0].house">
<option v-for="house in houses" :value="house.id">{{ $t(`falukant.house.type.${house.labelTr}`) }}</option>
</select>
</label>
<div class="action-buttons">
<button @click="saveUser" :disabled="!hasUserChanges">{{ $t('common.save') }}</button>
<button @click="deleteUser" class="button-secondary">{{ $t('common.delete') }}</button>
</div>
</div>
</div>
<!-- Niederlassungen Tab -->
<div v-else-if="activeTab === 'branches'" class="tab-pane">
<div v-if="loading.branches" class="loading">{{ $t('loading') }}</div>
<div v-else class="branches-section">
<h3>{{ $t('admin.falukant.edituser.branches.title') }}</h3>
<div v-for="branch in userBranches" :key="branch.id" class="branch-card">
<h4>{{ branch.region.name }} - {{ $t(`falukant.branch.types.${branch.branchType.labelTr}`) }}</h4>
<!-- Lager-Übersicht -->
<div class="warehouse-section">
<h5>{{ $t('falukant.branch.storage.title') }}</h5>
<div v-if="branch.stocks && branch.stocks.length" class="stock-list">
<div v-for="stock in branch.stocks" :key="stock.id" class="stock-item">
<span class="stock-name">{{ $t(`falukant.branch.stocktype.${stock.stockType.labelTr}`) }}</span>
<input type="number" v-model="stock.quantity" class="stock-quantity" />
<button @click="updateStock(stock)">{{ $t('common.update') }}</button>
</div>
</div>
<div v-else class="no-stocks">
{{ $t('admin.falukant.edituser.branches.noStocks') }}
</div>
<div v-if="canAddStock(branch)" class="add-stock-section">
<button @click="openAddStockDialog(branch.id)" class="add-stock-btn">
{{ $t('admin.falukant.edituser.branches.addStock') }}
</button>
</div>
<div v-else-if="branch.stocks && branch.stocks.length > 0" class="all-stocks-added">
{{ $t('admin.falukant.edituser.branches.allStocksAdded') }}
</div>
</div>
</div>
<div v-if="!userBranches.length" class="no-branches">
{{ $t('admin.falukant.edituser.branches.noBranches') }}
</div>
</div>
</div>
</div>
</div>
<!-- Add Stock Dialog -->
<div v-if="showAddStockDialog" class="dialog-overlay" @click="closeAddStockDialog">
<div class="dialog" @click.stop>
<h3>{{ $t('admin.falukant.edituser.branches.addStock') }}</h3>
<div class="dialog-content">
<div class="form-group">
<label>{{ $t('admin.falukant.edituser.branches.stockType') }}:</label>
<select v-model="newStock.stockTypeId" class="form-select">
<option value="">{{ $t('admin.falukant.edituser.branches.selectStockType') }}</option>
<option v-for="stockType in availableStockTypes" :key="stockType.id" :value="stockType.id">
{{ $t(`falukant.branch.stocktype.${stockType.labelTr}`) }}
</option>
</select>
</div>
<div class="form-group">
<label>{{ $t('admin.falukant.edituser.branches.quantity') }}:</label>
<input type="number" v-model="newStock.quantity" min="1" class="form-input" />
</div>
</div>
<div class="dialog-actions">
<button @click="closeAddStockDialog" class="btn btn-secondary">
{{ $t('common.cancel') }}
</button>
<button @click="addStock" class="btn btn-primary">
{{ $t('common.add') }}
</button>
</div>
</div>
</div>
</div>
</template>
<script lang="js">
import { mapState } from 'vuex';
import { mapActions } from 'vuex';
import SimpleTabs from '@/components/SimpleTabs.vue';
import apiClient from '@/utils/axios.js';
import { showApiError, showError, showSuccess } from '@/utils/feedback.js';
export default {
name: 'AdminFalukantEditUserView',
components: {
SimpleTabs
},
data() {
return {
user: {
username: '',
characterName: ''
},
users: [],
editableUser: null,
age: null,
originalAge: null,
originalUser: null,
titles: [],
houses: [],
activeTab: 'userdata',
tabs: [
{ value: 'userdata', label: 'admin.falukant.edituser.tabs.userdata' },
{ value: 'branches', label: 'admin.falukant.edituser.tabs.branches' }
],
userBranches: [],
stockTypes: [],
newStock: {
branchId: null,
stockTypeId: null,
quantity: 0
},
showAddStockDialog: false,
loading: {
branches: false,
stockTypes: false
}
}
},
computed: {
...mapState('falukant', ['user']),
canSearch() {
return this.user.username.trim().length > 0 || this.user.characterName.trim().length > 0;
},
hasUserChanges() {
if (!this.editableUser || !this.originalUser) return false;
return this.editableUser.falukantData[0].money != this.originalUser.falukantData[0].money
|| this.editableUser.falukantData[0].character.title_of_nobility != this.originalUser.falukantData[0].character.title_of_nobility
|| this.originalAge != this.age;
},
availableStockTypes() {
if (!this.newStock.branchId || !this.stockTypes.length) {
return this.stockTypes;
}
// Finde den aktuellen Branch
const currentBranch = this.userBranches.find(branch => branch.id === this.newStock.branchId);
if (!currentBranch || !currentBranch.stocks) {
return this.stockTypes;
}
// Erstelle eine Liste der bereits vorhandenen Stock-Type-IDs für diesen Branch
const existingStockTypeIds = currentBranch.stocks.map(stock => stock.stockTypeId);
// Filtere die Stock-Types, die noch nicht in diesem Branch vorhanden sind
return this.stockTypes.filter(stockType =>
!existingStockTypeIds.includes(stockType.id)
);
}
},
async mounted() {
const titlesResult = await apiClient.get('/api/falukant/nobility/titels');
this.titles = titlesResult.data;
// Lade Stock-Types
await this.loadStockTypes();
},
methods: {
async searchUser() {
if (!this.canSearch) {
showError(this, 'Bitte Benutzername oder Charaktername eingeben.');
return;
}
const userResult = await apiClient.post('/api/admin/falukant/searchuser', {
userName: this.user.username,
characterName: this.user.characterName
});
this.users = userResult.data;
},
async selectUser(user) {
const userResult = await apiClient.get(`/api/admin/falukant/getuser/${user.id}`);
this.editableUser = userResult.data;
this.originalUser = JSON.parse(JSON.stringify(this.editableUser));
this.age = Math.floor((Date.now() - new Date(this.editableUser.falukantData[0].character.birthdate)) / (24 * 60 * 60 * 1000));
this.originalAge = this.age;
this.users = [];
this.activeTab = 'userdata';
},
async saveUser() {
const dataToChange = {
id: this.editableUser.falukantData[0].id,
};
if (this.editableUser.falukantData[0].money != this.originalUser.falukantData[0].money) {
dataToChange.money = this.editableUser.falukantData[0].money;
}
if (this.editableUser.falukantData[0].character.title_of_nobility != this.originalUser.falukantData[0].character.title_of_nobility) {
dataToChange.title_of_nobility = this.editableUser.falukantData[0].character.title_of_nobility;
}
if (this.originalAge != this.age) {
dataToChange.age = this.age;
}
try {
await apiClient.post(`/api/admin/falukant/edituser`, dataToChange);
showSuccess(this, 'tr:admin.falukant.edituser.success');
} catch (error) {
showApiError(this, error, 'tr:admin.falukant.edituser.error');
}
},
async deleteUser() {
const dataToChange = {
id: this.editableUser.falukantData[0].id,
};
},
onTabChange(tab) {
if (tab === 'branches' && this.editableUser && !this.userBranches.length) {
this.loadUserBranches();
}
},
async loadUserBranches() {
if (!this.editableUser) return;
this.loading.branches = true;
try {
const branchesResult = await apiClient.get(`/api/admin/falukant/branches/${this.editableUser.falukantData[0].id}`);
this.userBranches = branchesResult.data;
} catch (error) {
console.error('Error loading user branches:', error);
showApiError(this, error, 'tr:admin.falukant.edituser.errorLoadingBranches');
} finally {
this.loading.branches = false;
}
},
async updateStock(stock) {
try {
await apiClient.put(`/api/admin/falukant/stock/${stock.id}`, {
quantity: stock.quantity
});
showSuccess(this, 'tr:admin.falukant.edituser.stockUpdated');
} catch (error) {
console.error('Error updating stock:', error);
this.$root.$refs.errorDialog.open('tr:admin.falukant.edituser.errorUpdatingStock');
}
},
async loadStockTypes() {
console.log('Loading stock types...');
this.loading.stockTypes = true;
try {
const stockTypesResult = await apiClient.get('/api/admin/falukant/stock-types');
console.log('Stock types loaded:', stockTypesResult.data);
this.stockTypes = stockTypesResult.data;
} catch (error) {
console.error('Error loading stock types:', error);
this.$root.$refs.errorDialog.open('tr:admin.falukant.edituser.errorLoadingStockTypes');
} finally {
this.loading.stockTypes = false;
}
},
openAddStockDialog(branchId) {
this.newStock.branchId = branchId;
this.newStock.stockTypeId = null;
this.newStock.quantity = 0;
this.showAddStockDialog = true;
},
closeAddStockDialog() {
this.showAddStockDialog = false;
this.newStock = {
branchId: null,
stockTypeId: null,
quantity: 0
};
},
async addStock() {
if (!this.newStock.stockTypeId || this.newStock.quantity <= 0) {
this.$root.$refs.errorDialog.open('tr:admin.falukant.edituser.invalidStockData');
return;
}
try {
await apiClient.post('/api/admin/falukant/stock', {
branchId: this.newStock.branchId,
stockTypeId: this.newStock.stockTypeId,
quantity: this.newStock.quantity
});
this.$root.$refs.messageDialog.open('tr:admin.falukant.edituser.stockAdded');
this.closeAddStockDialog();
// Lade Branches neu um den neuen Stock anzuzeigen
await this.loadUserBranches();
} catch (error) {
console.error('Error adding stock:', error);
this.$root.$refs.errorDialog.open('tr:admin.falukant.edituser.errorAddingStock');
}
},
canAddStock(branch) {
console.log('canAddStock called for branch:', branch);
console.log('stockTypes:', this.stockTypes);
console.log('branch.stocks:', branch.stocks);
// Wenn keine Stock-Types geladen sind, zeige den Button nicht
if (!this.stockTypes || this.stockTypes.length === 0) {
console.log('No stock types loaded, returning false');
return false;
}
// Wenn keine Stocks vorhanden sind, kann immer hinzugefügt werden
if (!branch.stocks || branch.stocks.length === 0) {
console.log('No stocks in branch, returning true');
return true;
}
// Erstelle eine Liste der bereits vorhandenen Stock-Type-IDs für diesen Branch
const existingStockTypeIds = branch.stocks.map(stock => stock.stockTypeId);
// Prüfe ob es noch Stock-Types gibt, die nicht in diesem Branch vorhanden sind
const availableStockTypes = this.stockTypes.filter(stockType =>
!existingStockTypeIds.includes(stockType.id)
);
console.log('Available stock types:', availableStockTypes);
return availableStockTypes.length > 0;
}
}
}
</script>
<style lang="scss" scoped>
.edit-user-view {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
height: 100%;
overflow-y: auto;
}
.search-section {
margin-bottom: 20px;
padding: 15px;
background: #f5f5f5;
border-radius: 5px;
}
.search-section label {
display: inline-block;
margin-right: 15px;
margin-bottom: 10px;
}
.search-section input {
margin-left: 5px;
padding: 5px;
border: 1px solid #ddd;
border-radius: 3px;
}
.search-section button {
padding: 8px 15px;
background: #1976d2;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
}
.user-list {
list-style: none;
padding: 0;
margin: 20px 0;
}
.user-list > li {
cursor: pointer;
color: #0066ff;
padding: 8px;
border: 1px solid #ddd;
margin-bottom: 5px;
border-radius: 3px;
background: #f9f9f9;
}
.user-list > li:hover {
background: #e9e9e9;
}
.user-edit-section {
margin-top: 30px;
border: 1px solid #ddd;
border-radius: 5px;
padding: 20px;
background: white;
max-height: calc(100vh - 200px);
overflow-y: auto;
}
.user-edit-section h2, .user-edit-section h3 {
margin-top: 0;
color: #333;
}
.tab-content {
margin-top: 20px;
max-height: calc(100vh - 300px);
overflow-y: auto;
}
.tab-pane {
padding: 20px 0;
min-height: 400px;
}
.edit-form label {
display: block;
margin-bottom: 15px;
}
.edit-form input, .edit-form select {
margin-left: 10px;
padding: 5px;
border: 1px solid #ddd;
border-radius: 3px;
}
.action-buttons {
margin-top: 20px;
}
.action-buttons button {
margin-right: 10px;
padding: 8px 15px;
border: none;
border-radius: 3px;
cursor: pointer;
}
.action-buttons button:first-child {
background: #4caf50;
color: white;
}
.action-buttons button:last-child {
background: #f44336;
color: white;
}
.branches-section {
max-height: calc(100vh - 400px);
overflow-y: auto;
}
.branches-section h3 {
margin-bottom: 20px;
color: #333;
position: sticky;
top: 0;
background: white;
padding: 10px 0;
z-index: 1;
}
.branch-card {
border: 1px solid #ddd;
border-radius: 5px;
padding: 15px;
margin-bottom: 20px;
background: #f9f9f9;
}
.branch-card h4 {
margin-top: 0;
color: #1976d2;
}
.warehouse-section {
margin-top: 15px;
}
.warehouse-section h5 {
margin-bottom: 10px;
color: #666;
}
.stock-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.stock-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px;
background: white;
border: 1px solid #ddd;
border-radius: 3px;
}
.stock-name {
flex: 1;
font-weight: bold;
}
.stock-quantity {
width: 80px;
padding: 5px;
border: 1px solid #ddd;
border-radius: 3px;
}
.stock-item button {
padding: 5px 10px;
background: #4caf50;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
}
.no-stocks, .no-branches {
text-align: center;
color: #666;
font-style: italic;
padding: 20px;
background: #f5f5f5;
border-radius: 3px;
}
.loading {
text-align: center;
color: #666;
font-style: italic;
padding: 20px;
}
/* Add Stock Dialog Styles */
.dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.dialog {
background: white;
border-radius: 8px;
padding: 20px;
min-width: 400px;
max-width: 500px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}
.dialog h3 {
margin: 0 0 20px 0;
color: #333;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
.dialog-content {
margin-bottom: 20px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #555;
}
.form-select, .form-input {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.form-select:focus, .form-input:focus {
outline: none;
border-color: #1976d2;
box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.2);
}
.dialog-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
}
.btn-primary {
background: #1976d2;
color: white;
}
.btn-primary:hover {
background: #1565c0;
}
.btn-secondary {
background: #6c757d;
color: white;
}
.btn-secondary:hover {
background: #5a6268;
}
/* Add Stock Button */
.add-stock-section {
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid #eee;
}
.add-stock-btn {
background: #28a745;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.add-stock-btn:hover {
background: #218838;
}
.all-stocks-added {
margin-top: 15px;
padding: 10px;
background: #e8f5e8;
border: 1px solid #c3e6c3;
border-radius: 4px;
color: #2d5a2d;
font-size: 14px;
text-align: center;
}
</style>