Refactor SaleSection component: Simplify sell item and sell all logic, remove unnecessary state management, and improve UI feedback. Update translations and clean up unused code in i18n files. Optimize price loading in BranchView and remove legacy product loading in MoneyHistoryView. Streamline PoliticsView by removing own character ID handling and related logic.
This commit is contained in:
@@ -93,8 +93,6 @@ class FalukantController {
|
||||
return result;
|
||||
});
|
||||
this.setHeir = this._wrapWithUser((userId, req) => this.service.setHeir(userId, req.body.childCharacterId));
|
||||
this.getPotentialHeirs = this._wrapWithUser((userId) => this.service.getPotentialHeirs(userId));
|
||||
this.selectHeir = this._wrapWithUser((userId, req) => this.service.selectHeir(userId, req.body.heirId));
|
||||
this.acceptMarriageProposal = this._wrapWithUser((userId, req) => this.service.acceptMarriageProposal(userId, req.body.proposalId));
|
||||
this.getGifts = this._wrapWithUser((userId) => {
|
||||
console.log('🔍 getGifts called with userId:', userId);
|
||||
@@ -118,12 +116,6 @@ class FalukantController {
|
||||
}, { successStatus: 201 });
|
||||
this.getParties = this._wrapWithUser((userId) => this.service.getParties(userId));
|
||||
|
||||
this.getReputationActions = this._wrapWithUser((userId) => this.service.getReputationActions(userId));
|
||||
this.executeReputationAction = this._wrapWithUser((userId, req) => {
|
||||
const { actionTypeId } = req.body;
|
||||
return this.service.executeReputationAction(userId, actionTypeId);
|
||||
}, { successStatus: 201 });
|
||||
|
||||
this.getNotBaptisedChildren = this._wrapWithUser((userId) => this.service.getNotBaptisedChildren(userId));
|
||||
this.baptise = this._wrapWithUser((userId, req) => {
|
||||
const { characterId: childId, firstName } = req.body;
|
||||
@@ -148,20 +140,6 @@ class FalukantController {
|
||||
|
||||
this.getPoliticsOverview = this._wrapWithUser((userId) => this.service.getPoliticsOverview(userId));
|
||||
this.getOpenPolitics = this._wrapWithUser((userId) => this.service.getOpenPolitics(userId));
|
||||
|
||||
// Church career endpoints
|
||||
this.getChurchOverview = this._wrapWithUser((userId) => this.service.getChurchOverview(userId));
|
||||
this.getAvailableChurchPositions = this._wrapWithUser((userId) => this.service.getAvailableChurchPositions(userId));
|
||||
this.applyForChurchPosition = this._wrapWithUser((userId, req) => {
|
||||
const { officeTypeId, regionId } = req.body;
|
||||
return this.service.applyForChurchPosition(userId, officeTypeId, regionId);
|
||||
}, { successStatus: 201 });
|
||||
this.getSupervisedApplications = this._wrapWithUser((userId) => this.service.getSupervisedApplications(userId));
|
||||
this.decideOnChurchApplication = this._wrapWithUser((userId, req) => {
|
||||
const { applicationId, decision } = req.body;
|
||||
return this.service.decideOnChurchApplication(userId, applicationId, decision);
|
||||
});
|
||||
this.hasChurchCareer = this._wrapWithUser((userId) => this.service.hasChurchCareer(userId));
|
||||
this.getElections = this._wrapWithUser((userId) => this.service.getElections(userId));
|
||||
this.vote = this._wrapWithUser((userId, req) => this.service.vote(userId, req.body.votes));
|
||||
this.applyForElections = this._wrapWithUser((userId, req) => this.service.applyForElections(userId, req.body.electionIds));
|
||||
@@ -176,18 +154,6 @@ class FalukantController {
|
||||
}
|
||||
return this.service.getProductPriceInRegion(userId, productId, regionId);
|
||||
});
|
||||
this.getProductPricesInRegionBatch = this._wrapWithUser((userId, req) => {
|
||||
const productIds = req.query.productIds;
|
||||
const regionId = parseInt(req.query.regionId, 10);
|
||||
if (!productIds || Number.isNaN(regionId)) {
|
||||
throw new Error('productIds (comma-separated) and regionId are required');
|
||||
}
|
||||
const productIdArray = productIds.split(',').map(id => parseInt(id.trim(), 10)).filter(id => !Number.isNaN(id));
|
||||
if (productIdArray.length === 0) {
|
||||
throw new Error('At least one valid productId is required');
|
||||
}
|
||||
return this.service.getProductPricesInRegionBatch(userId, productIdArray, regionId);
|
||||
});
|
||||
this.getProductPricesInCities = this._wrapWithUser((userId, req) => {
|
||||
const productId = parseInt(req.query.productId, 10);
|
||||
const currentPrice = parseFloat(req.query.currentPrice);
|
||||
|
||||
@@ -10,11 +10,20 @@ RegionData.init({
|
||||
allowNull: false},
|
||||
regionTypeId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: RegionType,
|
||||
key: 'id',
|
||||
schema: 'falukant_type'
|
||||
}
|
||||
},
|
||||
parentId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'region',
|
||||
key: 'id',
|
||||
schema: 'falukant_data'}
|
||||
},
|
||||
map: {
|
||||
type: DataTypes.JSONB,
|
||||
|
||||
@@ -6,7 +6,8 @@ class FalukantStock extends Model { }
|
||||
FalukantStock.init({
|
||||
branchId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
stockTypeId: {
|
||||
type: DataTypes.INTEGER,
|
||||
|
||||
@@ -16,6 +16,17 @@ ProductType.init({
|
||||
sellCost: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false}
|
||||
,
|
||||
sellCostMinNeutral: {
|
||||
type: DataTypes.DECIMAL,
|
||||
allowNull: true,
|
||||
field: 'sell_cost_min_neutral'
|
||||
},
|
||||
sellCostMaxNeutral: {
|
||||
type: DataTypes.DECIMAL,
|
||||
allowNull: true,
|
||||
field: 'sell_cost_max_neutral'
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'ProductType',
|
||||
|
||||
@@ -39,8 +39,6 @@ router.get('/directors', falukantController.getAllDirectors);
|
||||
router.post('/directors', falukantController.updateDirector);
|
||||
router.post('/family/acceptmarriageproposal', falukantController.acceptMarriageProposal);
|
||||
router.post('/family/set-heir', falukantController.setHeir);
|
||||
router.get('/heirs/potential', falukantController.getPotentialHeirs);
|
||||
router.post('/heirs/select', falukantController.selectHeir);
|
||||
router.get('/family/gifts', falukantController.getGifts);
|
||||
router.get('/family/children', falukantController.getChildren);
|
||||
router.post('/family/gift', falukantController.sendGift);
|
||||
@@ -55,16 +53,8 @@ router.post('/houses', falukantController.buyUserHouse);
|
||||
router.get('/party/types', falukantController.getPartyTypes);
|
||||
router.post('/party', falukantController.createParty);
|
||||
router.get('/party', falukantController.getParties);
|
||||
router.get('/reputation/actions', falukantController.getReputationActions);
|
||||
router.post('/reputation/actions', falukantController.executeReputationAction);
|
||||
router.get('/family/notbaptised', falukantController.getNotBaptisedChildren);
|
||||
router.post('/church/baptise', falukantController.baptise);
|
||||
router.get('/church/overview', falukantController.getChurchOverview);
|
||||
router.get('/church/positions/available', falukantController.getAvailableChurchPositions);
|
||||
router.post('/church/positions/apply', falukantController.applyForChurchPosition);
|
||||
router.get('/church/applications/supervised', falukantController.getSupervisedApplications);
|
||||
router.post('/church/applications/decide', falukantController.decideOnChurchApplication);
|
||||
router.get('/church/career/check', falukantController.hasChurchCareer);
|
||||
router.get('/education', falukantController.getEducation);
|
||||
router.post('/education', falukantController.sendToSchool);
|
||||
router.get('/bank/overview', falukantController.getBankOverview);
|
||||
@@ -82,7 +72,6 @@ router.get('/politics/open', falukantController.getOpenPolitics);
|
||||
router.post('/politics/open', falukantController.applyForElections);
|
||||
router.get('/cities', falukantController.getRegions);
|
||||
router.get('/products/price-in-region', falukantController.getProductPriceInRegion);
|
||||
router.get('/products/prices-in-region-batch', falukantController.getProductPricesInRegionBatch);
|
||||
router.get('/products/prices-in-cities', falukantController.getProductPricesInCities);
|
||||
router.get('/branches/:branchId/taxes', falukantController.getBranchTaxes);
|
||||
router.get('/vehicles/types', falukantController.getVehicleTypes);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -282,7 +282,11 @@ async function initializeFalukantProducts() {
|
||||
{ labelTr: 'ox', category: 5, productionTime: 5, sellCost: 60 },
|
||||
];
|
||||
|
||||
const productsToInsert = baseProducts;
|
||||
const productsToInsert = baseProducts.map(p => ({
|
||||
...p,
|
||||
sellCostMinNeutral: Math.ceil(p.sellCost * factorMin),
|
||||
sellCostMaxNeutral: Math.ceil(p.sellCost * factorMax),
|
||||
}));
|
||||
|
||||
await ProductType.bulkCreate(productsToInsert, {
|
||||
ignoreDuplicates: true,
|
||||
|
||||
@@ -104,14 +104,6 @@
|
||||
/>
|
||||
{{ $t('falukant.branch.director.starttransport') }}
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="director.mayRepairVehicles"
|
||||
@change="saveSetting('mayRepairVehicles', director.mayRepairVehicles)"
|
||||
/>
|
||||
{{ $t('falukant.branch.director.repairVehicles') }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
@@ -181,7 +173,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<NewDirectorDialog ref="newDirectorDialog" @directorHired="handleDirectorHired" />
|
||||
<NewDirectorDialog ref="newDirectorDialog" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -276,11 +268,6 @@ export default {
|
||||
this.$refs.newDirectorDialog.open(this.branchId);
|
||||
},
|
||||
|
||||
async handleDirectorHired() {
|
||||
// Nach dem Einstellen eines Direktors die Daten neu laden
|
||||
await this.loadDirector();
|
||||
},
|
||||
|
||||
async updateDirector() {
|
||||
if (!this.director || this.editIncome == null) return;
|
||||
try {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,10 +20,8 @@
|
||||
<td>{{ item.quality }}</td>
|
||||
<td>{{ item.totalQuantity }}</td>
|
||||
<td>
|
||||
<input type="number" v-model.number="item.sellQuantity" :min="1" :max="item.totalQuantity" :disabled="sellingItemIndex === index" />
|
||||
<button @click="sellItem(index)" :disabled="sellingItemIndex === index || sellingAll">
|
||||
{{ sellingItemIndex === index ? $t('falukant.branch.sale.selling') : $t('falukant.branch.sale.sellButton') }}
|
||||
</button>
|
||||
<input type="number" v-model.number="item.sellQuantity" :min="1" :max="item.totalQuantity" />
|
||||
<button @click="sellItem(index)">{{ $t('falukant.branch.sale.sellButton') }}</button>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="item.betterPrices && item.betterPrices.length > 0" class="price-cities">
|
||||
@@ -38,12 +36,7 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<button @click="sellAll" :disabled="sellingAll || sellingItemIndex !== null">
|
||||
{{ sellingAll ? $t('falukant.branch.sale.selling') : $t('falukant.branch.sale.sellAllButton') }}
|
||||
</button>
|
||||
<div v-if="sellAllStatus" class="sell-all-status" :class="sellAllStatus.type">
|
||||
{{ sellAllStatus.message }}
|
||||
</div>
|
||||
<button @click="sellAll">{{ $t('falukant.branch.sale.sellAllButton') }}</button>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p>{{ $t('falukant.branch.sale.noInventory') }}</p>
|
||||
@@ -190,9 +183,6 @@
|
||||
data() {
|
||||
return {
|
||||
inventory: [],
|
||||
sellingItemIndex: null,
|
||||
sellingAll: false,
|
||||
sellAllStatus: null,
|
||||
transportForm: {
|
||||
sourceKey: null,
|
||||
vehicleTypeId: null,
|
||||
@@ -261,6 +251,13 @@
|
||||
return new Date(a.eta).getTime() - new Date(b.eta).getTime();
|
||||
});
|
||||
},
|
||||
speedLabel(value) {
|
||||
const key = value == null ? 'unknown' : String(value);
|
||||
const tKey = `falukant.branch.transport.speed.${key}`;
|
||||
const translated = this.$t(tKey);
|
||||
if (!translated || translated === tKey) return value;
|
||||
return translated;
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadInventory();
|
||||
@@ -277,22 +274,12 @@
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
speedLabel(value) {
|
||||
// Muss in methods liegen (Vue3): in computed wäre es ein Getter und keine aufrufbare Funktion.
|
||||
const key = value == null ? 'unknown' : String(value);
|
||||
const tKey = `falukant.branch.transport.speed.${key}`;
|
||||
const translated = this.$t(tKey);
|
||||
if (!translated || translated === tKey) return value;
|
||||
return translated;
|
||||
},
|
||||
async loadInventory() {
|
||||
try {
|
||||
const response = await apiClient.get(`/api/falukant/inventory/${this.branchId}`);
|
||||
this.inventory = response.data.map(item => ({
|
||||
...item,
|
||||
sellQuantity: item.totalQuantity,
|
||||
// Vue3: besserPrices direkt als Property setzen (statt this.$set)
|
||||
betterPrices: Array.isArray(item.betterPrices) ? item.betterPrices : [],
|
||||
}));
|
||||
await this.loadPricesForInventory();
|
||||
} catch (error) {
|
||||
@@ -313,11 +300,10 @@
|
||||
currentPrice: currentPrice
|
||||
}
|
||||
});
|
||||
// Vue3: direkte Zuweisung ist reaktiv
|
||||
item.betterPrices = Array.isArray(data) ? data : [];
|
||||
this.$set(item, 'betterPrices', data || []);
|
||||
} catch (error) {
|
||||
console.error(`Error loading prices for item ${itemKey}:`, error);
|
||||
item.betterPrices = [];
|
||||
this.$set(item, 'betterPrices', []);
|
||||
} finally {
|
||||
this.loadingPrices.delete(itemKey);
|
||||
}
|
||||
@@ -334,61 +320,23 @@
|
||||
maximumFractionDigits: 2,
|
||||
}).format(price);
|
||||
},
|
||||
async sellItem(index) {
|
||||
if (this.sellingItemIndex !== null || this.sellingAll) return;
|
||||
|
||||
sellItem(index) {
|
||||
const item = this.inventory[index];
|
||||
const quantityToSell = item.sellQuantity || item.totalQuantity;
|
||||
this.sellingItemIndex = index;
|
||||
|
||||
try {
|
||||
await apiClient.post(`/api/falukant/sell`, {
|
||||
apiClient.post(`/api/falukant/sell`, {
|
||||
branchId: this.branchId,
|
||||
productId: item.product.id,
|
||||
quantity: quantityToSell,
|
||||
quality: item.quality,
|
||||
});
|
||||
// UI sofort freigeben (Label/Disabled zurücksetzen), dann Inventory refreshen
|
||||
this.sellingItemIndex = null;
|
||||
await this.loadInventory();
|
||||
} catch (error) {
|
||||
}).catch(() => {
|
||||
alert(this.$t('falukant.branch.sale.sellError'));
|
||||
} finally {
|
||||
this.sellingItemIndex = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
async sellAll() {
|
||||
if (this.sellingAll || this.sellingItemIndex !== null) return;
|
||||
|
||||
this.sellingAll = true;
|
||||
this.sellAllStatus = null;
|
||||
|
||||
try {
|
||||
const response = await apiClient.post(`/api/falukant/sell/all`, { branchId: this.branchId });
|
||||
const revenue = response.data?.revenue || 0;
|
||||
// UI sofort freigeben + Status setzen, danach Inventory refreshen
|
||||
this.sellingAll = false;
|
||||
this.sellAllStatus = {
|
||||
type: 'success',
|
||||
message: this.$t('falukant.branch.sale.sellAllSuccess', { revenue: this.formatMoney(revenue) })
|
||||
};
|
||||
// Inventory neu laden nach erfolgreichem Verkauf
|
||||
await this.loadInventory();
|
||||
} catch (error) {
|
||||
// UI sofort freigeben + Fehlerstatus setzen
|
||||
this.sellingAll = false;
|
||||
this.sellAllStatus = {
|
||||
type: 'error',
|
||||
message: this.$t('falukant.branch.sale.sellAllError')
|
||||
};
|
||||
} finally {
|
||||
// Falls noch nicht freigegeben (z.B. wenn ein unerwarteter Fehler vor Response passiert)
|
||||
this.sellingAll = false;
|
||||
// Status nach 5 Sekunden löschen
|
||||
setTimeout(() => {
|
||||
this.sellAllStatus = null;
|
||||
}, 5000);
|
||||
}
|
||||
sellAll() {
|
||||
apiClient.post(`/api/falukant/sell/all`, { branchId: this.branchId })
|
||||
.catch(() => {
|
||||
alert(this.$t('falukant.branch.sale.sellAllError'));
|
||||
});
|
||||
},
|
||||
inventoryOptions() {
|
||||
return this.inventory.map((item, index) => ({
|
||||
@@ -627,11 +575,11 @@
|
||||
cursor: help;
|
||||
}
|
||||
.city-price-green {
|
||||
background-color: var(--color-primary-green);
|
||||
background-color: #90EE90;
|
||||
color: #000;
|
||||
}
|
||||
.city-price-orange {
|
||||
background-color: var(--color-primary-orange);
|
||||
background-color: #FFA500;
|
||||
color: #000;
|
||||
}
|
||||
.city-price-red {
|
||||
@@ -642,19 +590,5 @@
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
}
|
||||
.sell-all-status {
|
||||
margin-top: 10px;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.sell-all-status.success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
.sell-all-status.error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -32,9 +32,8 @@
|
||||
},
|
||||
"notifications": {
|
||||
"notify_election_created": "Es wurde eine neue Wahl ausgeschrieben.",
|
||||
"notify_office_filled": "Ein politisches Amt wurde neu besetzt.",
|
||||
"production": {
|
||||
"overproduction": "Überproduktion: Deine Produktion liegt {value} Einheiten über dem Bedarf{branch_info}."
|
||||
"overproduction": "Überproduktion: Deine Produktion liegt {value}% über dem Bedarf."
|
||||
},
|
||||
"transport": {
|
||||
"waiting": "Transport wartet"
|
||||
@@ -136,14 +135,6 @@
|
||||
"store": "Verkauf",
|
||||
"fullstack": "Produktion mit Verkauf"
|
||||
}
|
||||
},
|
||||
"heirSelection": {
|
||||
"title": "Charakter verloren - Erben auswählen",
|
||||
"description": "Dein Charakter wurde durch einen Fehler verloren. Bitte wähle einen Erben aus deiner Hauptregion aus, um fortzufahren.",
|
||||
"loading": "Lade mögliche Erben...",
|
||||
"noHeirs": "Es wurden keine passenden Erben gefunden.",
|
||||
"select": "Als Erben wählen",
|
||||
"error": "Fehler beim Auswählen des Erben."
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
@@ -241,7 +232,6 @@
|
||||
"produce": "Darf produzieren",
|
||||
"sell": "Darf verkaufen",
|
||||
"starttransport": "Darf Transporte veranlassen",
|
||||
"repairVehicles": "Darf Fahrzeuge reparieren",
|
||||
"emptyTransport": {
|
||||
"title": "Transport ohne Produkte",
|
||||
"description": "Bewege Transportmittel von dieser Niederlassung zu einer anderen, um sie besser zu nutzen.",
|
||||
@@ -270,10 +260,6 @@
|
||||
"sell": "Verkauf",
|
||||
"sellButton": "Verkaufen",
|
||||
"sellAllButton": "Alles verkaufen",
|
||||
"selling": "Verkauf läuft...",
|
||||
"sellError": "Fehler beim Verkauf des Produkts.",
|
||||
"sellAllError": "Fehler beim Verkauf aller Produkte.",
|
||||
"sellAllSuccess": "Alle Produkte wurden erfolgreich verkauft. Einnahmen: {revenue}",
|
||||
"transportTitle": "Transport anlegen",
|
||||
"transportSource": "Artikel",
|
||||
"transportSourcePlaceholder": "Artikel wählen",
|
||||
@@ -321,10 +307,7 @@
|
||||
"current": "Laufende Produktionen",
|
||||
"product": "Produkt",
|
||||
"remainingTime": "Verbleibende Zeit (Sekunden)",
|
||||
"noProductions": "Keine laufenden Produktionen.",
|
||||
"status": "Status",
|
||||
"sleep": "Zurückgestellt",
|
||||
"active": "Aktiv"
|
||||
"noProductions": "Keine laufenden Produktionen."
|
||||
},
|
||||
"columns": {
|
||||
"city": "Stadt",
|
||||
@@ -595,7 +578,6 @@
|
||||
"Production cost": "Produktionskosten",
|
||||
"Sell all products": "Alle Produkte verkauft",
|
||||
"sell products": "Produkte verkauft",
|
||||
"taxFromSaleProduct": "Steuer aus Verkauf: {product}",
|
||||
"director starts production": "Direktor beginnt Produktion",
|
||||
"director payed out": "Direktorgehalt ausgezahlt",
|
||||
"Buy storage (type: field)": "Lagerplatz gekauft (Typ: Feld)",
|
||||
@@ -614,9 +596,6 @@
|
||||
"new nobility title": "Neuer Adelstitel",
|
||||
"partyOrder": "Fest bestellt",
|
||||
"renovation_all": "Haus komplett renoviert",
|
||||
"reputationAction": {
|
||||
"school_funding": "Sozialstatus: Schule/Lehrstuhl finanziert"
|
||||
},
|
||||
"health": {
|
||||
"pill": "Gesundheitsmaßnahme: Tablette",
|
||||
"doctor": "Gesundheitsmaßnahme: Arztbesuch",
|
||||
@@ -759,8 +738,7 @@
|
||||
"reputation": {
|
||||
"title": "Reputation",
|
||||
"overview": {
|
||||
"title": "Übersicht",
|
||||
"current": "Deine aktuelle Reputation"
|
||||
"title": "Übersicht"
|
||||
},
|
||||
"party": {
|
||||
"title": "Feste",
|
||||
@@ -799,34 +777,6 @@
|
||||
"type": "Festart",
|
||||
"cost": "Kosten",
|
||||
"date": "Datum"
|
||||
},
|
||||
"actions": {
|
||||
"title": "Aktionen",
|
||||
"description": "Mit diesen Aktionen kannst du Reputation gewinnen. Je öfter du dieselbe Aktion ausführst, desto weniger Reputation bringt sie (unabhängig von den Kosten).",
|
||||
"action": "Aktion",
|
||||
"cost": "Kosten",
|
||||
"gain": "Reputation",
|
||||
"timesUsed": "Bereits genutzt",
|
||||
"execute": "Ausführen",
|
||||
"running": "Läuft...",
|
||||
"none": "Keine Aktionen verfügbar.",
|
||||
"dailyLimit": "Heute noch verfügbar: {remaining} / {cap} Reputation (durch Aktionen).",
|
||||
"cooldown": "Nächste Sozialstatus-Aktion in ca. {minutes} Minuten möglich.",
|
||||
"success": "Aktion erfolgreich! Reputation +{gain}, Kosten {cost}.",
|
||||
"successSimple": "Aktion erfolgreich!",
|
||||
"type": {
|
||||
"library_donation": "Spende für eine Bibliothek",
|
||||
"orphanage_build": "Waisenhaus aufbauen",
|
||||
"statue_build": "Statue errichten",
|
||||
"hospital_donation": "Krankenhaus/Heilhaus stiften",
|
||||
"school_funding": "Schule/Lehrstuhl finanzieren",
|
||||
"well_build": "Brunnen/Wasserwerk bauen",
|
||||
"bridge_build": "Straßen-/Brückenbau finanzieren",
|
||||
"soup_kitchen": "Armenspeisung organisieren",
|
||||
"patronage": "Kunst & Mäzenatentum",
|
||||
"church_hospice": "Hospiz-/Kirchenspende",
|
||||
"scholarships": "Stipendienfonds finanzieren"
|
||||
}
|
||||
}
|
||||
},
|
||||
"party": {
|
||||
@@ -838,58 +788,6 @@
|
||||
}
|
||||
},
|
||||
"church": {
|
||||
"title": "Kirche",
|
||||
"tabs": {
|
||||
"current": "Aktuelle Positionen",
|
||||
"available": "Verfügbare Positionen",
|
||||
"applications": "Bewerbungen"
|
||||
},
|
||||
"current": {
|
||||
"office": "Amt",
|
||||
"region": "Region",
|
||||
"holder": "Inhaber",
|
||||
"supervisor": "Vorgesetzter",
|
||||
"none": "Keine aktuellen Positionen vorhanden."
|
||||
},
|
||||
"available": {
|
||||
"office": "Amt",
|
||||
"region": "Region",
|
||||
"supervisor": "Vorgesetzter",
|
||||
"seats": "Verfügbare Plätze",
|
||||
"action": "Aktion",
|
||||
"apply": "Bewerben",
|
||||
"applySuccess": "Bewerbung erfolgreich eingereicht.",
|
||||
"applyError": "Fehler beim Einreichen der Bewerbung.",
|
||||
"none": "Keine verfügbaren Positionen."
|
||||
},
|
||||
"applications": {
|
||||
"office": "Amt",
|
||||
"region": "Region",
|
||||
"applicant": "Bewerber",
|
||||
"date": "Datum",
|
||||
"action": "Aktion",
|
||||
"approve": "Annehmen",
|
||||
"reject": "Ablehnen",
|
||||
"approveSuccess": "Bewerbung angenommen.",
|
||||
"rejectSuccess": "Bewerbung abgelehnt.",
|
||||
"decideError": "Fehler bei der Entscheidung.",
|
||||
"none": "Keine Bewerbungen vorhanden."
|
||||
},
|
||||
"offices": {
|
||||
"village-priest": "Dorfgeistlicher",
|
||||
"parish-priest": "Pfarrer",
|
||||
"dean": "Dekan",
|
||||
"archdeacon": "Erzdiakon",
|
||||
"bishop": "Bischof",
|
||||
"archbishop": "Erzbischof",
|
||||
"cardinal": "Kardinal",
|
||||
"pope": "Papst"
|
||||
},
|
||||
"application": {
|
||||
"received": "Neue Bewerbung erhalten",
|
||||
"approved": "Bewerbung angenommen",
|
||||
"rejected": "Bewerbung abgelehnt"
|
||||
},
|
||||
"title": "Kirche",
|
||||
"baptism": {
|
||||
"title": "Taufen",
|
||||
@@ -985,9 +883,6 @@
|
||||
"success": "Erfolg",
|
||||
"selectMeasure": "Maßnahme",
|
||||
"perform": "Durchführen",
|
||||
"errors": {
|
||||
"tooClose": "Aktionen zu dicht hintereinander (maximal 1× pro 24 Stunden)."
|
||||
},
|
||||
"measures": {
|
||||
"pill": "Tablette",
|
||||
"doctor": "Arztbesuch",
|
||||
|
||||
@@ -18,9 +18,8 @@
|
||||
},
|
||||
"notifications": {
|
||||
"notify_election_created": "A new election has been scheduled.",
|
||||
"notify_office_filled": "A political office has been filled.",
|
||||
"production": {
|
||||
"overproduction": "Overproduction: your production is {value} units above demand{branch_info}."
|
||||
"overproduction": "Overproduction: your production is {value}% above demand."
|
||||
},
|
||||
"transport": {
|
||||
"waiting": "Transport waiting"
|
||||
@@ -101,12 +100,6 @@
|
||||
"bad": "Bad",
|
||||
"very_bad": "Very bad"
|
||||
},
|
||||
"healthview": {
|
||||
"title": "Health",
|
||||
"errors": {
|
||||
"tooClose": "Actions too close together (max once per 24 hours)."
|
||||
}
|
||||
},
|
||||
"moneyHistory": {
|
||||
"title": "Money history",
|
||||
"filter": "Filter",
|
||||
@@ -123,7 +116,6 @@
|
||||
"Production cost": "Production cost",
|
||||
"Sell all products": "Sell all products",
|
||||
"sell products": "Sell products",
|
||||
"taxFromSaleProduct": "Tax from product sale: {product}",
|
||||
"director starts production": "Director starts production",
|
||||
"director payed out": "Director salary paid out",
|
||||
"Buy storage (type: field)": "Bought storage (type: field)",
|
||||
@@ -142,9 +134,6 @@
|
||||
"new nobility title": "New title of nobility",
|
||||
"partyOrder": "Party ordered",
|
||||
"renovation_all": "House fully renovated",
|
||||
"reputationAction": {
|
||||
"school_funding": "Social status: funded a school/chair"
|
||||
},
|
||||
"health": {
|
||||
"pill": "Health measure: pill",
|
||||
"doctor": "Health measure: doctor",
|
||||
@@ -174,8 +163,7 @@
|
||||
},
|
||||
"director": {
|
||||
"income": "Income",
|
||||
"incomeUpdated": "Salary has been successfully updated.",
|
||||
"repairVehicles": "May repair vehicles"
|
||||
"incomeUpdated": "Salary has been successfully updated."
|
||||
},
|
||||
"vehicles": {
|
||||
"cargo_cart": "Cargo cart",
|
||||
@@ -193,31 +181,8 @@
|
||||
"storage": "Storage",
|
||||
"transport": "Transport",
|
||||
"taxes": "Taxes"
|
||||
},
|
||||
"production": {
|
||||
"title": "Production",
|
||||
"info": "Details about production in the branch.",
|
||||
"selectProduct": "Select product",
|
||||
"quantity": "Quantity",
|
||||
"storageAvailable": "Free storage",
|
||||
"cost": "Cost",
|
||||
"duration": "Duration",
|
||||
"revenue": "Revenue",
|
||||
"start": "Start production",
|
||||
"success": "Production started successfully!",
|
||||
"error": "Error starting production.",
|
||||
"minutes": "Minutes",
|
||||
"ending": "Completed:",
|
||||
"time": "Time",
|
||||
"current": "Running productions",
|
||||
"product": "Product",
|
||||
"remainingTime": "Remaining time (seconds)",
|
||||
"noProductions": "No running productions.",
|
||||
"status": "Status",
|
||||
"sleep": "Suspended",
|
||||
"active": "Active"
|
||||
},
|
||||
"taxes": {
|
||||
}
|
||||
,"taxes": {
|
||||
"title": "Taxes",
|
||||
"loading": "Loading tax data...",
|
||||
"loadingError": "Failed to load tax data: {error}",
|
||||
@@ -230,80 +195,9 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"overview": {
|
||||
"title": "Falukant - Overview",
|
||||
"metadata": {
|
||||
"title": "Personal",
|
||||
"name": "Name",
|
||||
"money": "Wealth",
|
||||
"age": "Age",
|
||||
"mainbranch": "Home City",
|
||||
"nobleTitle": "Status"
|
||||
},
|
||||
"productions": {
|
||||
"title": "Productions"
|
||||
},
|
||||
"stock": {
|
||||
"title": "Stock"
|
||||
},
|
||||
"branches": {
|
||||
"title": "Branches",
|
||||
"level": {
|
||||
"production": "Production",
|
||||
"store": "Store",
|
||||
"fullstack": "Production with Store"
|
||||
}
|
||||
},
|
||||
"heirSelection": {
|
||||
"title": "Character Lost - Select Heir",
|
||||
"description": "Your character was lost due to an error. Please select an heir from your main region to continue.",
|
||||
"loading": "Loading potential heirs...",
|
||||
"noHeirs": "No suitable heirs were found.",
|
||||
"select": "Select as Heir",
|
||||
"error": "Error selecting heir."
|
||||
}
|
||||
},
|
||||
"nobility": {
|
||||
"cooldown": "You can only advance again on {date}."
|
||||
},
|
||||
"reputation": {
|
||||
"title": "Reputation",
|
||||
"overview": {
|
||||
"title": "Overview",
|
||||
"current": "Your current reputation"
|
||||
},
|
||||
"party": {
|
||||
"title": "Parties"
|
||||
},
|
||||
"actions": {
|
||||
"title": "Actions",
|
||||
"description": "These actions let you gain reputation. The more often you repeat the same action, the less reputation it yields (independent of cost).",
|
||||
"action": "Action",
|
||||
"cost": "Cost",
|
||||
"gain": "Reputation",
|
||||
"timesUsed": "Times used",
|
||||
"execute": "Execute",
|
||||
"running": "Running...",
|
||||
"none": "No actions available.",
|
||||
"dailyLimit": "Available today: {remaining} / {cap} reputation (from actions).",
|
||||
"cooldown": "Next social status action available in about {minutes} minutes.",
|
||||
"success": "Action successful! Reputation +{gain}, cost {cost}.",
|
||||
"successSimple": "Action successful!",
|
||||
"type": {
|
||||
"library_donation": "Donate to a library",
|
||||
"orphanage_build": "Build an orphanage",
|
||||
"statue_build": "Erect a statue",
|
||||
"hospital_donation": "Found a hospital/infirmary",
|
||||
"school_funding": "Fund a school/chair",
|
||||
"well_build": "Build a well/waterworks",
|
||||
"bridge_build": "Fund roads/bridges",
|
||||
"soup_kitchen": "Organize a soup kitchen",
|
||||
"patronage": "Arts & patronage",
|
||||
"church_hospice": "Hospice/church donation",
|
||||
"scholarships": "Fund scholarships"
|
||||
}
|
||||
}
|
||||
},
|
||||
"branchProduction": {
|
||||
"storageAvailable": "Free storage"
|
||||
},
|
||||
@@ -377,76 +271,6 @@
|
||||
"assessor": "Assessor"
|
||||
}
|
||||
},
|
||||
"church": {
|
||||
"title": "Church",
|
||||
"tabs": {
|
||||
"current": "Current Positions",
|
||||
"available": "Available Positions",
|
||||
"applications": "Applications"
|
||||
},
|
||||
"current": {
|
||||
"office": "Office",
|
||||
"region": "Region",
|
||||
"holder": "Holder",
|
||||
"supervisor": "Supervisor",
|
||||
"none": "No current positions available."
|
||||
},
|
||||
"available": {
|
||||
"office": "Office",
|
||||
"region": "Region",
|
||||
"supervisor": "Supervisor",
|
||||
"seats": "Available Seats",
|
||||
"action": "Action",
|
||||
"apply": "Apply",
|
||||
"applySuccess": "Application submitted successfully.",
|
||||
"applyError": "Error submitting application.",
|
||||
"none": "No available positions."
|
||||
},
|
||||
"applications": {
|
||||
"office": "Office",
|
||||
"region": "Region",
|
||||
"applicant": "Applicant",
|
||||
"date": "Date",
|
||||
"action": "Action",
|
||||
"approve": "Approve",
|
||||
"reject": "Reject",
|
||||
"approveSuccess": "Application approved.",
|
||||
"rejectSuccess": "Application rejected.",
|
||||
"decideError": "Error making decision.",
|
||||
"none": "No applications available."
|
||||
},
|
||||
"offices": {
|
||||
"village-priest": "Village Priest",
|
||||
"parish-priest": "Parish Priest",
|
||||
"dean": "Dean",
|
||||
"archdeacon": "Archdeacon",
|
||||
"bishop": "Bishop",
|
||||
"archbishop": "Archbishop",
|
||||
"cardinal": "Cardinal",
|
||||
"pope": "Pope"
|
||||
},
|
||||
"application": {
|
||||
"received": "New application received",
|
||||
"approved": "Application approved",
|
||||
"rejected": "Application rejected"
|
||||
},
|
||||
"baptism": {
|
||||
"title": "Baptism",
|
||||
"table": {
|
||||
"name": "First Name",
|
||||
"gender": "Gender",
|
||||
"age": "Age",
|
||||
"baptise": "Baptize (50)",
|
||||
"newName": "Suggest Name"
|
||||
},
|
||||
"gender": {
|
||||
"male": "Boy",
|
||||
"female": "Girl"
|
||||
},
|
||||
"success": "The child has been baptized.",
|
||||
"error": "The child could not be baptized."
|
||||
}
|
||||
},
|
||||
"family": {
|
||||
"children": {
|
||||
"title": "Children",
|
||||
|
||||
@@ -572,25 +572,9 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.products || this.products.length === 0) {
|
||||
this.productPricesCache = {};
|
||||
return;
|
||||
}
|
||||
|
||||
// OPTIMIERUNG: Lade alle Preise in einem Batch-Request
|
||||
try {
|
||||
const productIds = this.products.map(p => p.id).join(',');
|
||||
const { data } = await apiClient.get('/api/falukant/products/prices-in-region-batch', {
|
||||
params: {
|
||||
productIds: productIds,
|
||||
regionId: this.selectedBranch.regionId
|
||||
}
|
||||
});
|
||||
this.productPricesCache = data || {};
|
||||
} catch (error) {
|
||||
console.error('Error loading prices in batch:', error);
|
||||
// Fallback: Lade Preise einzeln (aber parallel)
|
||||
const pricePromises = this.products.map(async (product) => {
|
||||
// Lade Preise für alle Produkte in der aktuellen Region
|
||||
const prices = {};
|
||||
for (const product of this.products) {
|
||||
try {
|
||||
const { data } = await apiClient.get('/api/falukant/products/price-in-region', {
|
||||
params: {
|
||||
@@ -598,23 +582,17 @@ export default {
|
||||
regionId: this.selectedBranch.regionId
|
||||
}
|
||||
});
|
||||
return { productId: product.id, price: data.price };
|
||||
} catch (err) {
|
||||
console.error(`Error loading price for product ${product.id}:`, err);
|
||||
prices[product.id] = data.price;
|
||||
} catch (error) {
|
||||
console.error(`Error loading price for product ${product.id}:`, error);
|
||||
// Fallback auf Standard-Berechnung
|
||||
const knowledgeFactor = product.knowledges?.[0]?.knowledge || 0;
|
||||
const maxPrice = product.sellCost;
|
||||
const minPrice = maxPrice * 0.6;
|
||||
return { productId: product.id, price: minPrice + (maxPrice - minPrice) * (knowledgeFactor / 100) };
|
||||
prices[product.id] = minPrice + (maxPrice - minPrice) * (knowledgeFactor / 100);
|
||||
}
|
||||
});
|
||||
|
||||
const results = await Promise.all(pricePromises);
|
||||
this.productPricesCache = {};
|
||||
results.forEach(({ productId, price }) => {
|
||||
this.productPricesCache[productId] = price;
|
||||
});
|
||||
}
|
||||
this.productPricesCache = prices;
|
||||
},
|
||||
|
||||
formatPercent(value) {
|
||||
@@ -714,10 +692,7 @@ export default {
|
||||
},
|
||||
|
||||
conditionLabel(value) {
|
||||
// 0 ist ein gültiger Zustand (z.B. komplett kaputt) und darf nicht als "Unbekannt" enden.
|
||||
if (value === null || value === undefined) return 'Unbekannt';
|
||||
const v = Number(value);
|
||||
if (!Number.isFinite(v)) return 'Unbekannt';
|
||||
const v = Number(value) || 0;
|
||||
if (v >= 95) return 'Ausgezeichnet'; // 95–100
|
||||
if (v >= 72) return 'Sehr gut'; // 72–94
|
||||
if (v >= 54) return 'Gut'; // 54–71
|
||||
@@ -725,7 +700,7 @@ export default {
|
||||
if (v >= 22) return 'Schlecht'; // 22–38
|
||||
if (v >= 6) return 'Sehr schlecht'; // 6–21
|
||||
if (v >= 1) return 'Katastrophal'; // 1–5
|
||||
return 'Katastrophal'; // 0 oder kleiner
|
||||
return 'Unbekannt';
|
||||
},
|
||||
|
||||
speedLabel(value) {
|
||||
@@ -1039,15 +1014,12 @@ export default {
|
||||
});
|
||||
await this.loadVehicles();
|
||||
this.closeRepairAllVehiclesDialog();
|
||||
// Statt JS-alert: Dialog schließen und MessageDialog anzeigen
|
||||
this.$root.$refs.messageDialog?.open('tr:falukant.branch.transport.repairAllSuccess');
|
||||
alert(this.$t('falukant.branch.transport.repairAllSuccess'));
|
||||
this.$refs.statusBar?.fetchStatus();
|
||||
} catch (error) {
|
||||
console.error('Error repairing all vehicles:', error);
|
||||
const errorMessage = error.response?.data?.message || this.$t('falukant.branch.transport.repairAllError');
|
||||
// Bestätigungsdialog ebenfalls schließen und Fehler im MessageDialog anzeigen
|
||||
this.closeRepairAllVehiclesDialog();
|
||||
this.$root.$refs.messageDialog?.open(String(errorMessage), this.$t('error.title'));
|
||||
alert(errorMessage);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1104,15 +1076,12 @@ export default {
|
||||
await apiClient.post(`/api/falukant/vehicles/${this.repairVehicleDialog.vehicle.id}/repair`);
|
||||
await this.loadVehicles();
|
||||
this.closeRepairVehicleDialog();
|
||||
// Statt JS-alert: Dialog schließen und MessageDialog anzeigen
|
||||
this.$root.$refs.messageDialog?.open('tr:falukant.branch.transport.repairSuccess');
|
||||
alert(this.$t('falukant.branch.transport.repairSuccess'));
|
||||
this.$refs.statusBar?.fetchStatus();
|
||||
} catch (error) {
|
||||
console.error('Error repairing vehicle:', error);
|
||||
const errorMessage = error.response?.data?.message || this.$t('falukant.branch.transport.repairError');
|
||||
// Bestätigungsdialog ebenfalls schließen und Fehler im MessageDialog anzeigen
|
||||
this.closeRepairVehicleDialog();
|
||||
this.$root.$refs.messageDialog?.open(String(errorMessage), this.$t('error.title'));
|
||||
alert(errorMessage);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -67,28 +67,12 @@ export default {
|
||||
currentPage: 1,
|
||||
totalPages: 1,
|
||||
},
|
||||
productsById: {},
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
await Promise.all([this.loadProducts(), this.fetchMoneyHistory(1)]);
|
||||
await this.fetchMoneyHistory(1);
|
||||
},
|
||||
methods: {
|
||||
async loadProducts() {
|
||||
try {
|
||||
const { data } = await apiClient.get('/api/falukant/products');
|
||||
const map = {};
|
||||
for (const p of (data || [])) {
|
||||
if (p && p.id != null && p.labelTr) {
|
||||
map[String(p.id)] = p.labelTr;
|
||||
}
|
||||
}
|
||||
this.productsById = map;
|
||||
} catch (e) {
|
||||
console.error('Error loading products for money history', e);
|
||||
this.productsById = {};
|
||||
}
|
||||
},
|
||||
async fetchMoneyHistory(page) {
|
||||
try {
|
||||
const response = await apiClient.post('/api/falukant/moneyhistory', {
|
||||
@@ -101,25 +85,6 @@ export default {
|
||||
}
|
||||
},
|
||||
translateActivity(activity) {
|
||||
try {
|
||||
const raw = String(activity ?? '');
|
||||
// Handle legacy format: "tax from sale product 3"
|
||||
const m = raw.match(/^tax\s+from\s+sale\s+product\s+(\d+)$/i);
|
||||
if (m && m[1]) {
|
||||
const id = m[1];
|
||||
const labelTr = this.productsById[String(id)];
|
||||
const productName = labelTr ? this.$t(`falukant.product.${labelTr}`) : `#${id}`;
|
||||
return this.$t('falukant.moneyHistory.activities.taxFromSaleProduct', { product: productName });
|
||||
}
|
||||
// New/structured format: "taxFromSaleProduct.<labelTr>"
|
||||
if (raw.startsWith('taxFromSaleProduct.')) {
|
||||
const labelTr = raw.substring('taxFromSaleProduct.'.length);
|
||||
const productName = labelTr ? this.$t(`falukant.product.${labelTr}`) : labelTr;
|
||||
return this.$t('falukant.moneyHistory.activities.taxFromSaleProduct', { product: productName });
|
||||
}
|
||||
} catch (_) {
|
||||
// ignore and fall back
|
||||
}
|
||||
// Handle nested keys like "health.pill" -> "health.pill"
|
||||
const key = `falukant.moneyHistory.activities.${activity}`;
|
||||
const translation = this.$t(key);
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="pos in currentPositions" :key="pos.id" :class="{ 'own-position': isOwnPosition(pos) }">
|
||||
<tr v-for="pos in currentPositions" :key="pos.id">
|
||||
<td>{{ $t(`falukant.politics.offices.${pos.officeType.name}`) }}</td>
|
||||
<td>{{ pos.region.name }}</td>
|
||||
<td>
|
||||
@@ -193,7 +193,6 @@ export default {
|
||||
elections: [],
|
||||
selectedCandidates: {},
|
||||
selectedApplications: [],
|
||||
ownCharacterId: null,
|
||||
loading: {
|
||||
current: false,
|
||||
openPolitics: false,
|
||||
@@ -210,8 +209,7 @@ export default {
|
||||
return this.elections.some(e => !e.voted);
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadOwnCharacterId();
|
||||
mounted() {
|
||||
this.loadCurrentPositions();
|
||||
},
|
||||
methods: {
|
||||
@@ -332,24 +330,6 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
async loadOwnCharacterId() {
|
||||
try {
|
||||
const { data } = await apiClient.get('/api/falukant/info');
|
||||
if (data.character && data.character.id) {
|
||||
this.ownCharacterId = data.character.id;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error loading own character ID', err);
|
||||
}
|
||||
},
|
||||
|
||||
isOwnPosition(pos) {
|
||||
if (!this.ownCharacterId || !pos.character) {
|
||||
return false;
|
||||
}
|
||||
return pos.character.id === this.ownCharacterId;
|
||||
},
|
||||
|
||||
async submitApplications() {
|
||||
try {
|
||||
const response = await apiClient.post(
|
||||
@@ -431,11 +411,6 @@ h2 {
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.politics-table tbody tr.own-position {
|
||||
background-color: #e0e0e0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
|
||||
Reference in New Issue
Block a user