Implement debtors prison features across the application: Enhance FalukantController to include debtors prison logic in various service methods. Update FalukantService to manage debtors prison state and integrate it into user data retrieval. Modify frontend components, including DashboardWidget, StatusBar, and BankView, to display debtors prison status and warnings. Add localization for debtors prison messages in English, German, and Spanish, ensuring clarity in user notifications and actions.
This commit is contained in:
@@ -8,6 +8,18 @@
|
||||
|
||||
<!-- OVERVIEW -->
|
||||
<div v-if="activeTab === 'account'">
|
||||
<div v-if="debtorsPrison.active" class="debt-status" :class="{ 'is-prison': debtorsPrison.inDebtorsPrison }">
|
||||
<h3>{{ debtStatusTitle }}</h3>
|
||||
<p>{{ debtStatusDescription }}</p>
|
||||
<div class="debt-status__meta">
|
||||
<span>{{ $t('falukant.bank.debtorsPrison.daysOverdue') }}: <strong>{{ debtorsPrison.daysOverdue }}</strong></span>
|
||||
<span>{{ $t('falukant.bank.debtorsPrison.creditworthiness') }}: <strong>{{ debtorsPrison.creditworthiness }}</strong></span>
|
||||
<span v-if="debtorsPrison.nextForcedAction">
|
||||
{{ $t('falukant.bank.debtorsPrison.nextForcedAction') }}:
|
||||
<strong>{{ $t(`falukant.bank.debtorsPrison.actions.${debtorsPrison.nextForcedAction}`) }}</strong>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="account-section">
|
||||
<table>
|
||||
<tr>
|
||||
@@ -26,6 +38,10 @@
|
||||
<td>{{ $t('falukant.bank.account.availableCredit') }}</td>
|
||||
<td>{{ formatCost(bankOverview.availableCredit) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('falukant.bank.debtorsPrison.creditworthiness') }}</td>
|
||||
<td>{{ bankOverview.creditworthiness }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -77,9 +93,12 @@
|
||||
<p>
|
||||
<strong>{{ $t('falukant.bank.credits.payoff.total') }}: {{ formatCost(creditCost()) }}</strong>
|
||||
</p>
|
||||
<button @click="confirmPayoff" class="button" :disabled="!selectedCredit">
|
||||
<button @click="confirmPayoff" class="button" :disabled="!selectedCredit || isCreditBlocked">
|
||||
{{ $t('falukant.bank.credits.payoff.confirm') }}
|
||||
</button>
|
||||
<p v-if="isCreditBlocked" class="payoff-hint payoff-hint--error">
|
||||
{{ $t('falukant.bank.debtorsPrison.creditBlocked') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -93,6 +112,7 @@ import StatusBar from '@/components/falukant/StatusBar.vue';
|
||||
import SimpleTabs from '@/components/SimpleTabs.vue';
|
||||
import apiClient from '@/utils/axios.js';
|
||||
import { mapState } from 'vuex';
|
||||
import { showError } from '@/utils/feedback.js';
|
||||
|
||||
export default {
|
||||
name: 'BankView',
|
||||
@@ -118,34 +138,114 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(['socket'])
|
||||
...mapState(['socket', 'daemonSocket', 'user']),
|
||||
debtorsPrison() {
|
||||
return this.bankOverview.debtorsPrison || {
|
||||
active: false,
|
||||
inDebtorsPrison: false,
|
||||
daysOverdue: 0,
|
||||
creditworthiness: 100,
|
||||
nextForcedAction: null
|
||||
};
|
||||
},
|
||||
isCreditBlocked() {
|
||||
return this.debtorsPrison.inDebtorsPrison;
|
||||
},
|
||||
debtStatusTitle() {
|
||||
return this.debtorsPrison.inDebtorsPrison
|
||||
? this.$t('falukant.bank.debtorsPrison.titlePrison')
|
||||
: this.$t('falukant.bank.debtorsPrison.titleWarning');
|
||||
},
|
||||
debtStatusDescription() {
|
||||
return this.debtorsPrison.inDebtorsPrison
|
||||
? this.$t('falukant.bank.debtorsPrison.descriptionPrison')
|
||||
: this.$t('falukant.bank.debtorsPrison.descriptionWarning');
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
socket(newVal, oldVal) {
|
||||
if (oldVal) this.teardownSocketEvents();
|
||||
if (newVal) this.setupSocketEvents();
|
||||
},
|
||||
daemonSocket(newVal, oldVal) {
|
||||
if (oldVal) this.teardownSocketEvents();
|
||||
if (newVal) this.setupSocketEvents();
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadBankOverview();
|
||||
this.setupSocketEvents();
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.socket) {
|
||||
this.socket.off('falukantUpdateStatus', this.loadBankOverview);
|
||||
if (this._pendingRefresh) {
|
||||
clearTimeout(this._pendingRefresh);
|
||||
this._pendingRefresh = null;
|
||||
}
|
||||
this.teardownSocketEvents();
|
||||
},
|
||||
methods: {
|
||||
matchesCurrentUser(eventData) {
|
||||
if (eventData?.user_id == null) {
|
||||
return true;
|
||||
}
|
||||
const currentIds = [this.user?.id, this.user?.hashedId]
|
||||
.filter(Boolean)
|
||||
.map((value) => String(value));
|
||||
return currentIds.includes(String(eventData.user_id));
|
||||
},
|
||||
setupSocketEvents() {
|
||||
this.teardownSocketEvents();
|
||||
if (this.socket) {
|
||||
this.socket.on('falukantUpdateStatus', (data) => {
|
||||
this.handleEvent({ event: 'falukantUpdateStatus', ...data });
|
||||
});
|
||||
} else {
|
||||
setTimeout(() => this.setupSocketEvents(), 1000);
|
||||
this._statusSocketHandler = (data) => this.handleEvent({ event: 'falukantUpdateStatus', ...data });
|
||||
this._familySocketHandler = (data) => this.handleEvent({ event: 'falukantUpdateFamily', ...data });
|
||||
this._debtSocketHandler = (data) => this.handleEvent({ event: 'falukantUpdateDebt', ...data });
|
||||
this.socket.on('falukantUpdateStatus', this._statusSocketHandler);
|
||||
this.socket.on('falukantUpdateFamily', this._familySocketHandler);
|
||||
this.socket.on('falukantUpdateDebt', this._debtSocketHandler);
|
||||
}
|
||||
if (this.daemonSocket) {
|
||||
this._daemonHandler = (event) => this.handleDaemonMessage(event);
|
||||
this.daemonSocket.addEventListener('message', this._daemonHandler);
|
||||
}
|
||||
},
|
||||
teardownSocketEvents() {
|
||||
if (this.socket) {
|
||||
if (this._statusSocketHandler) this.socket.off('falukantUpdateStatus', this._statusSocketHandler);
|
||||
if (this._familySocketHandler) this.socket.off('falukantUpdateFamily', this._familySocketHandler);
|
||||
if (this._debtSocketHandler) this.socket.off('falukantUpdateDebt', this._debtSocketHandler);
|
||||
}
|
||||
if (this.daemonSocket && this._daemonHandler) {
|
||||
this.daemonSocket.removeEventListener('message', this._daemonHandler);
|
||||
this._daemonHandler = null;
|
||||
}
|
||||
},
|
||||
handleEvent(eventData) {
|
||||
if (!this.matchesCurrentUser(eventData)) {
|
||||
return;
|
||||
}
|
||||
switch (eventData.event) {
|
||||
case 'falukantUpdateStatus':
|
||||
this.loadBankOverview();
|
||||
this.queueBankRefresh();
|
||||
break;
|
||||
case 'falukantUpdateDebt':
|
||||
this.queueBankRefresh();
|
||||
break;
|
||||
case 'falukantUpdateFamily':
|
||||
if (['monthly', 'lover_installment'].includes(eventData.reason)) {
|
||||
this.queueBankRefresh();
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
queueBankRefresh() {
|
||||
if (this._pendingRefresh) {
|
||||
clearTimeout(this._pendingRefresh);
|
||||
}
|
||||
this._pendingRefresh = setTimeout(() => {
|
||||
this._pendingRefresh = null;
|
||||
this.loadBankOverview();
|
||||
}, 120);
|
||||
},
|
||||
async loadBankOverview() {
|
||||
try {
|
||||
const { data } = await apiClient.get('/api/falukant/bank/overview');
|
||||
@@ -155,6 +255,7 @@ export default {
|
||||
}
|
||||
},
|
||||
async confirmPayoff() {
|
||||
if (this.isCreditBlocked) return;
|
||||
try {
|
||||
await apiClient.post('/api/falukant/bank/credits', {
|
||||
height: this.selectedCredit
|
||||
@@ -163,16 +264,17 @@ export default {
|
||||
this.selectedCredit = null;
|
||||
this.activeTab = 'credits';
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
showError(err.response?.data?.error || this.$t('falukant.bank.debtorsPrison.creditError'));
|
||||
}
|
||||
},
|
||||
handleDaemonMessage(msg) {
|
||||
try {
|
||||
if (['falukantUpdateStatus', 'moneyChange', 'creditChange'].includes(msg.event)) {
|
||||
this.loadBankOverview();
|
||||
const data = JSON.parse(msg.data);
|
||||
if (['falukantUpdateStatus', 'falukantUpdateDebt'].includes(data.event)) {
|
||||
this.handleEvent(data);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(evt, err);
|
||||
console.error(err);
|
||||
}
|
||||
},
|
||||
feeRate() {
|
||||
@@ -190,4 +292,38 @@ export default {
|
||||
|
||||
<style scoped lang="scss">
|
||||
h2 { padding-top: 20px; }
|
||||
|
||||
.debt-status {
|
||||
margin-bottom: 1rem;
|
||||
padding: 1rem 1.1rem;
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid rgba(180, 120, 40, 0.35);
|
||||
background: linear-gradient(180deg, rgba(255, 244, 223, 0.95), rgba(255, 249, 238, 0.98));
|
||||
}
|
||||
|
||||
.debt-status.is-prison {
|
||||
border-color: rgba(146, 57, 40, 0.45);
|
||||
background: linear-gradient(180deg, rgba(255, 232, 225, 0.96), rgba(255, 245, 241, 0.98));
|
||||
}
|
||||
|
||||
.debt-status h3 {
|
||||
margin-bottom: 0.35rem;
|
||||
}
|
||||
|
||||
.debt-status__meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
margin-top: 0.75rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.payoff-hint {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.payoff-hint--error {
|
||||
color: #8b2f23;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -15,9 +15,28 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
v-if="debtorsPrison.active"
|
||||
class="branch-debt-warning surface-card"
|
||||
:class="{ 'is-prison': debtorsPrison.inDebtorsPrison }"
|
||||
>
|
||||
<strong>
|
||||
{{ debtorsPrison.inDebtorsPrison
|
||||
? $t('falukant.bank.debtorsPrison.titlePrison')
|
||||
: $t('falukant.bank.debtorsPrison.titleWarning') }}
|
||||
</strong>
|
||||
<p>
|
||||
{{ debtorsPrison.inDebtorsPrison
|
||||
? $t('falukant.branch.debtorsPrison.branchLocked')
|
||||
: $t('falukant.branch.debtorsPrison.branchRisk') }}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<BranchSelection
|
||||
:branches="branches"
|
||||
:selectedBranch="selectedBranch"
|
||||
:blocked="debtorsPrison.inDebtorsPrison"
|
||||
:blocked-reason="debtorsPrison.inDebtorsPrison ? $t('falukant.branch.debtorsPrison.selectionBlocked') : ''"
|
||||
@branchSelected="onBranchSelected"
|
||||
@createBranch="createBranch"
|
||||
@upgradeBranch="upgradeBranch"
|
||||
@@ -404,6 +423,10 @@ export default {
|
||||
branchTaxesLoading: false,
|
||||
branchTaxesError: null,
|
||||
currentCertificate: null,
|
||||
debtorsPrison: {
|
||||
active: false,
|
||||
inDebtorsPrison: false
|
||||
},
|
||||
pendingBranchRefresh: null,
|
||||
};
|
||||
},
|
||||
@@ -462,6 +485,7 @@ export default {
|
||||
// Live-Socket-Events (Backend Socket.io)
|
||||
if (this.socket) {
|
||||
this.socket.on('falukantUpdateStatus', (data) => this.handleEvent({ event: 'falukantUpdateStatus', ...data }));
|
||||
this.socket.on('falukantUpdateDebt', (data) => this.handleEvent({ event: 'falukantUpdateDebt', ...data }));
|
||||
this.socket.on('falukantUpdateProductionCertificate', (data) => this.handleEvent({ event: 'falukantUpdateProductionCertificate', ...data }));
|
||||
this.socket.on('falukantBranchUpdate', (data) => this.handleEvent({ event: 'falukantBranchUpdate', ...data }));
|
||||
this.socket.on('transport_arrived', (data) => this.handleEvent({ event: 'transport_arrived', ...data }));
|
||||
@@ -482,6 +506,7 @@ export default {
|
||||
}
|
||||
if (this.socket) {
|
||||
this.socket.off('falukantUpdateStatus');
|
||||
this.socket.off('falukantUpdateDebt');
|
||||
this.socket.off('falukantUpdateProductionCertificate');
|
||||
this.socket.off('falukantBranchUpdate');
|
||||
this.socket.off('transport_arrived');
|
||||
@@ -558,6 +583,10 @@ export default {
|
||||
try {
|
||||
const result = await apiClient.get('/api/falukant/user');
|
||||
this.currentCertificate = result.data?.certificate ?? null;
|
||||
this.debtorsPrison = result.data?.debtorsPrison || {
|
||||
active: false,
|
||||
inDebtorsPrison: false
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error loading certificate:', error);
|
||||
}
|
||||
@@ -678,6 +707,10 @@ export default {
|
||||
},
|
||||
|
||||
async createBranch() {
|
||||
if (this.debtorsPrison.inDebtorsPrison) {
|
||||
showError(this, this.$t('falukant.branch.debtorsPrison.selectionBlocked'));
|
||||
return;
|
||||
}
|
||||
await this.loadBranches();
|
||||
// Nach dem Anlegen eines neuen Branches automatisch den
|
||||
// zuletzt/neu erstellten Branch auswählen.
|
||||
@@ -694,6 +727,10 @@ export default {
|
||||
|
||||
async upgradeBranch() {
|
||||
if (!this.selectedBranch) return;
|
||||
if (this.debtorsPrison.inDebtorsPrison) {
|
||||
showError(this, this.$t('falukant.branch.debtorsPrison.selectionBlocked'));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await apiClient.post('/api/falukant/branches/upgrade', {
|
||||
branchId: this.selectedBranch.id,
|
||||
@@ -851,6 +888,7 @@ export default {
|
||||
this.$refs.productionSection?.loadStorage();
|
||||
break;
|
||||
case 'falukantUpdateStatus':
|
||||
case 'falukantUpdateDebt':
|
||||
case 'falukantUpdateProductionCertificate':
|
||||
case 'falukantBranchUpdate':
|
||||
this.queueBranchRefresh();
|
||||
@@ -1184,6 +1222,23 @@ export default {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.branch-debt-warning {
|
||||
margin-bottom: 16px;
|
||||
padding: 16px 18px;
|
||||
border: 1px solid rgba(180, 120, 40, 0.32);
|
||||
background: linear-gradient(180deg, rgba(255, 244, 223, 0.95), rgba(255, 250, 239, 0.98));
|
||||
}
|
||||
|
||||
.branch-debt-warning.is-prison {
|
||||
border-color: rgba(146, 57, 40, 0.4);
|
||||
background: linear-gradient(180deg, rgba(255, 232, 225, 0.96), rgba(255, 245, 241, 0.98));
|
||||
}
|
||||
|
||||
.branch-debt-warning p {
|
||||
margin: 6px 0 0;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.branch-hero__meta {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,23 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
v-if="debtorsPrison.active"
|
||||
class="family-debt-warning surface-card"
|
||||
:class="{ 'is-prison': debtorsPrison.inDebtorsPrison }"
|
||||
>
|
||||
<strong>
|
||||
{{ debtorsPrison.inDebtorsPrison
|
||||
? $t('falukant.bank.debtorsPrison.titlePrison')
|
||||
: $t('falukant.bank.debtorsPrison.titleWarning') }}
|
||||
</strong>
|
||||
<p>
|
||||
{{ debtorsPrison.inDebtorsPrison
|
||||
? $t('falukant.family.debtorsPrison.familyImpact')
|
||||
: $t('falukant.family.debtorsPrison.familyWarning') }}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div class="spouse-section">
|
||||
<h3>{{ $t('falukant.family.spouse.title') }}</h3>
|
||||
<div v-if="relationships.length > 0" class="relationship-container">
|
||||
@@ -386,6 +403,10 @@ export default {
|
||||
householdTension: null,
|
||||
householdTensionScore: null,
|
||||
householdTensionReasons: [],
|
||||
debtorsPrison: {
|
||||
active: false,
|
||||
inDebtorsPrison: false
|
||||
},
|
||||
selectedChild: null,
|
||||
pendingFamilyRefresh: null
|
||||
}
|
||||
@@ -426,11 +447,13 @@ export default {
|
||||
if (this.socket) {
|
||||
this._falukantUpdateStatusHandler = (data) => this.handleEvent({ event: 'falukantUpdateStatus', ...data });
|
||||
this._falukantUpdateFamilyHandler = (data) => this.handleEvent({ event: 'falukantUpdateFamily', ...data });
|
||||
this._falukantUpdateDebtHandler = (data) => this.handleEvent({ event: 'falukantUpdateDebt', ...data });
|
||||
this._childrenUpdateHandler = (data) => this.handleEvent({ event: 'children_update', ...data });
|
||||
this._familyChangedHandler = (data) => this.handleEvent({ event: 'familychanged', ...data });
|
||||
|
||||
this.socket.on('falukantUpdateStatus', this._falukantUpdateStatusHandler);
|
||||
this.socket.on('falukantUpdateFamily', this._falukantUpdateFamilyHandler);
|
||||
this.socket.on('falukantUpdateDebt', this._falukantUpdateDebtHandler);
|
||||
this.socket.on('children_update', this._childrenUpdateHandler);
|
||||
this.socket.on('familychanged', this._familyChangedHandler);
|
||||
} else {
|
||||
@@ -441,6 +464,7 @@ export default {
|
||||
if (!this.socket) return;
|
||||
if (this._falukantUpdateStatusHandler) this.socket.off('falukantUpdateStatus', this._falukantUpdateStatusHandler);
|
||||
if (this._falukantUpdateFamilyHandler) this.socket.off('falukantUpdateFamily', this._falukantUpdateFamilyHandler);
|
||||
if (this._falukantUpdateDebtHandler) this.socket.off('falukantUpdateDebt', this._falukantUpdateDebtHandler);
|
||||
if (this._childrenUpdateHandler) this.socket.off('children_update', this._childrenUpdateHandler);
|
||||
if (this._familyChangedHandler) this.socket.off('familychanged', this._familyChangedHandler);
|
||||
},
|
||||
@@ -454,6 +478,7 @@ export default {
|
||||
if ([
|
||||
'falukantUpdateStatus',
|
||||
'falukantUpdateFamily',
|
||||
'falukantUpdateDebt',
|
||||
'children_update',
|
||||
'falukantUpdateChurch',
|
||||
'familychanged',
|
||||
@@ -502,6 +527,7 @@ export default {
|
||||
|
||||
switch (eventData.event) {
|
||||
case 'falukantUpdateStatus':
|
||||
case 'falukantUpdateDebt':
|
||||
case 'familychanged':
|
||||
this.queueFamilyRefresh({ reloadCharacter: true });
|
||||
break;
|
||||
@@ -533,6 +559,10 @@ export default {
|
||||
this.householdTension = response.data.householdTension;
|
||||
this.householdTensionScore = response.data.householdTensionScore;
|
||||
this.householdTensionReasons = response.data.householdTensionReasons || [];
|
||||
this.debtorsPrison = response.data.debtorsPrison || {
|
||||
active: false,
|
||||
inDebtorsPrison: false
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error loading family data:', error);
|
||||
}
|
||||
@@ -874,6 +904,23 @@ export default {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.family-debt-warning {
|
||||
margin-bottom: 16px;
|
||||
padding: 16px 18px;
|
||||
border: 1px solid rgba(180, 120, 40, 0.32);
|
||||
background: linear-gradient(180deg, rgba(255, 244, 223, 0.95), rgba(255, 250, 239, 0.98));
|
||||
}
|
||||
|
||||
.family-debt-warning.is-prison {
|
||||
border-color: rgba(146, 57, 40, 0.4);
|
||||
background: linear-gradient(180deg, rgba(255, 232, 225, 0.96), rgba(255, 245, 241, 0.98));
|
||||
}
|
||||
|
||||
.family-debt-warning p {
|
||||
margin: 6px 0 0;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.marriage-overview {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
|
||||
@@ -2,6 +2,22 @@
|
||||
<div class="house-view">
|
||||
<StatusBar />
|
||||
<h2>{{ $t('falukant.house.title') }}</h2>
|
||||
<section
|
||||
v-if="debtorsPrison.active"
|
||||
class="house-debt-warning surface-card"
|
||||
:class="{ 'is-prison': debtorsPrison.inDebtorsPrison }"
|
||||
>
|
||||
<strong>
|
||||
{{ debtorsPrison.inDebtorsPrison
|
||||
? $t('falukant.bank.debtorsPrison.titlePrison')
|
||||
: $t('falukant.bank.debtorsPrison.titleWarning') }}
|
||||
</strong>
|
||||
<p>
|
||||
{{ debtorsPrison.inDebtorsPrison
|
||||
? $t('falukant.house.debtorsPrison.houseRisk')
|
||||
: $t('falukant.house.debtorsPrison.houseWarning') }}
|
||||
</p>
|
||||
</section>
|
||||
<div class="existing-house">
|
||||
<div :style="houseType ? houseStyle(houseType.position, 341) : {}" class="house"></div>
|
||||
<div class="status-panel surface-card">
|
||||
@@ -127,7 +143,7 @@
|
||||
<div class="buyable-house-price">
|
||||
{{ $t('falukant.house.price') }}: {{ buyCost(house) }}
|
||||
</div>
|
||||
<button @click="buyHouse(house.id)">
|
||||
<button @click="buyHouse(house.id)" :disabled="debtorsPrison.inDebtorsPrison">
|
||||
{{ $t('falukant.house.buy') }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -161,11 +177,15 @@ export default {
|
||||
servantPayLevel: 'normal',
|
||||
servantPayOptions: ['low', 'normal', 'high'],
|
||||
buyableHouses: [],
|
||||
currency: '€'
|
||||
currency: '€',
|
||||
debtorsPrison: {
|
||||
active: false,
|
||||
inDebtorsPrison: false
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(['socket']),
|
||||
...mapState(['socket', 'daemonSocket', 'user']),
|
||||
allRenovated() {
|
||||
return Object.values(this.status).every(v => v >= 100);
|
||||
}
|
||||
@@ -176,6 +196,10 @@ export default {
|
||||
const userRes = await apiClient.get('/api/falukant/houses');
|
||||
this.userHouse = userRes.data;
|
||||
this.houseType = this.userHouse.houseType;
|
||||
this.debtorsPrison = this.userHouse.debtorsPrison || {
|
||||
active: false,
|
||||
inDebtorsPrison: false
|
||||
};
|
||||
const { roofCondition, wallCondition, floorCondition, windowCondition } = this.userHouse;
|
||||
this.status = { roofCondition, wallCondition, floorCondition, windowCondition };
|
||||
this.servantSummary = this.userHouse.servantSummary || this.servantSummary;
|
||||
@@ -327,14 +351,27 @@ export default {
|
||||
handleDaemonMessage(evt) {
|
||||
try {
|
||||
const msg = JSON.parse(evt.data);
|
||||
if (msg.event === 'houseupdated') this.loadData();
|
||||
if (!this.matchesCurrentUser(msg)) return;
|
||||
if (['houseupdated', 'falukantUpdateStatus', 'falukantUpdateDebt', 'falukantHouseUpdate'].includes(msg.event)) this.loadData();
|
||||
} catch { }
|
||||
},
|
||||
matchesCurrentUser(eventData) {
|
||||
if (eventData?.user_id == null) {
|
||||
return true;
|
||||
}
|
||||
const currentIds = [this.user?.id, this.user?.hashedId]
|
||||
.filter(Boolean)
|
||||
.map((value) => String(value));
|
||||
return currentIds.includes(String(eventData.user_id));
|
||||
},
|
||||
setupSocketEvents() {
|
||||
if (this.socket) {
|
||||
this.socket.on('falukantHouseUpdate', (data) => {
|
||||
this.handleEvent({ event: 'falukantHouseUpdate', ...data });
|
||||
});
|
||||
this.socket.on('falukantUpdateDebt', (data) => {
|
||||
this.handleEvent({ event: 'falukantUpdateDebt', ...data });
|
||||
});
|
||||
this.socket.on('falukantUpdateStatus', (data) => {
|
||||
this.handleEvent({ event: 'falukantUpdateStatus', ...data });
|
||||
});
|
||||
@@ -345,6 +382,7 @@ export default {
|
||||
handleEvent(eventData) {
|
||||
switch (eventData.event) {
|
||||
case 'falukantUpdateStatus':
|
||||
case 'falukantUpdateDebt':
|
||||
case 'falukantHouseUpdate':
|
||||
this.loadData();
|
||||
break;
|
||||
@@ -354,10 +392,17 @@ export default {
|
||||
async mounted() {
|
||||
await this.loadData();
|
||||
this.setupSocketEvents();
|
||||
if (this.daemonSocket) {
|
||||
this.daemonSocket.addEventListener('message', this.handleDaemonMessage);
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.daemonSocket) {
|
||||
this.daemonSocket.removeEventListener('message', this.handleDaemonMessage);
|
||||
}
|
||||
if (this.socket) {
|
||||
this.socket.off('falukantHouseUpdate', this.loadData);
|
||||
this.socket.off('falukantUpdateDebt', this.loadData);
|
||||
this.socket.off('falukantUpdateStatus', this.loadData);
|
||||
}
|
||||
}
|
||||
@@ -387,6 +432,22 @@ h2 {
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.house-debt-warning {
|
||||
padding: 16px 18px;
|
||||
border: 1px solid rgba(180, 120, 40, 0.32);
|
||||
background: linear-gradient(180deg, rgba(255, 244, 223, 0.95), rgba(255, 250, 239, 0.98));
|
||||
}
|
||||
|
||||
.house-debt-warning.is-prison {
|
||||
border-color: rgba(146, 57, 40, 0.4);
|
||||
background: linear-gradient(180deg, rgba(255, 232, 225, 0.96), rgba(255, 245, 241, 0.98));
|
||||
}
|
||||
|
||||
.house-debt-warning p {
|
||||
margin: 6px 0 0;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.existing-house {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
|
||||
@@ -9,7 +9,24 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div v-if="falukantUser?.character" class="imagecontainer">
|
||||
<section
|
||||
v-if="falukantUser?.debtorsPrison?.active"
|
||||
class="falukant-debt-warning surface-card"
|
||||
:class="{ 'is-prison': falukantUser?.debtorsPrison?.inDebtorsPrison }"
|
||||
>
|
||||
<strong>
|
||||
{{ falukantUser?.debtorsPrison?.inDebtorsPrison
|
||||
? $t('falukant.bank.debtorsPrison.titlePrison')
|
||||
: $t('falukant.bank.debtorsPrison.titleWarning') }}
|
||||
</strong>
|
||||
<p>
|
||||
{{ falukantUser?.debtorsPrison?.inDebtorsPrison
|
||||
? $t('falukant.bank.debtorsPrison.descriptionPrison')
|
||||
: $t('falukant.bank.debtorsPrison.descriptionWarning') }}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div v-if="falukantUser?.character && !falukantUser?.debtorsPrison?.inDebtorsPrison" class="imagecontainer">
|
||||
<div :style="getAvatarStyle" class="avatar"></div>
|
||||
<div class="house-with-character">
|
||||
<div :style="getHouseStyle" class="house"></div>
|
||||
@@ -23,6 +40,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="falukantUser?.character && falukantUser?.debtorsPrison?.inDebtorsPrison" class="imagecontainer imagecontainer--prison">
|
||||
<div class="debtors-prison-visual" aria-hidden="true">
|
||||
<div class="debtors-prison-visual__moon"></div>
|
||||
<div class="debtors-prison-visual__tower"></div>
|
||||
<div class="debtors-prison-visual__bars debtors-prison-visual__bars--left"></div>
|
||||
<div class="debtors-prison-visual__bars debtors-prison-visual__bars--right"></div>
|
||||
<div class="debtors-prison-visual__ground"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section v-if="falukantUser?.character" class="falukant-summary-grid">
|
||||
<article class="summary-card surface-card">
|
||||
<span class="summary-card__label">{{ $t('falukant.overview.metadata.certificate') }}</span>
|
||||
@@ -44,6 +71,15 @@
|
||||
<strong>{{ stockEntryCount }}</strong>
|
||||
<p>Verdichteter Blick auf Warenbestand über alle Regionen.</p>
|
||||
</article>
|
||||
<article v-if="falukantUser?.debtorsPrison?.active" class="summary-card surface-card">
|
||||
<span class="summary-card__label">{{ $t('falukant.bank.debtorsPrison.creditworthiness') }}</span>
|
||||
<strong>{{ falukantUser.debtorsPrison.creditworthiness }}</strong>
|
||||
<p>
|
||||
{{ falukantUser.debtorsPrison.nextForcedAction
|
||||
? $t(`falukant.bank.debtorsPrison.actions.${falukantUser.debtorsPrison.nextForcedAction}`)
|
||||
: $t('falukant.bank.debtorsPrison.titleWarning') }}
|
||||
</p>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section v-if="falukantUser?.character" class="falukant-routine-grid">
|
||||
@@ -357,6 +393,7 @@ export default {
|
||||
this.socket.off("falukantUpdateStatus");
|
||||
this.socket.off("falukantUpdateFamily");
|
||||
this.socket.off("falukantUpdateChurch");
|
||||
this.socket.off("falukantUpdateDebt");
|
||||
this.socket.off("falukantUpdateProductionCertificate");
|
||||
this.socket.off("children_update");
|
||||
this.socket.off("falukantBranchUpdate");
|
||||
@@ -376,6 +413,9 @@ export default {
|
||||
this.socket.on("falukantUpdateChurch", (data) => {
|
||||
this.handleEvent({ event: 'falukantUpdateChurch', ...data });
|
||||
});
|
||||
this.socket.on("falukantUpdateDebt", (data) => {
|
||||
this.handleEvent({ event: 'falukantUpdateDebt', ...data });
|
||||
});
|
||||
this.socket.on("falukantUpdateProductionCertificate", (data) => {
|
||||
this.handleEvent({ event: 'falukantUpdateProductionCertificate', ...data });
|
||||
});
|
||||
@@ -446,6 +486,7 @@ export default {
|
||||
case 'falukantUpdateStatus':
|
||||
case 'falukantUpdateFamily':
|
||||
case 'falukantUpdateChurch':
|
||||
case 'falukantUpdateDebt':
|
||||
case 'falukantUpdateProductionCertificate':
|
||||
case 'children_update':
|
||||
case 'falukantBranchUpdate':
|
||||
@@ -579,6 +620,23 @@ export default {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.falukant-debt-warning {
|
||||
margin-bottom: 16px;
|
||||
padding: 16px 18px;
|
||||
border: 1px solid rgba(180, 120, 40, 0.32);
|
||||
background: linear-gradient(180deg, rgba(255, 244, 223, 0.95), rgba(255, 250, 239, 0.98));
|
||||
}
|
||||
|
||||
.falukant-debt-warning.is-prison {
|
||||
border-color: rgba(146, 57, 40, 0.4);
|
||||
background: linear-gradient(180deg, rgba(255, 232, 225, 0.96), rgba(255, 245, 241, 0.98));
|
||||
}
|
||||
|
||||
.falukant-debt-warning p {
|
||||
margin: 6px 0 0;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.falukant-summary-grid,
|
||||
.falukant-routine-grid {
|
||||
display: grid;
|
||||
@@ -694,6 +752,101 @@ export default {
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.imagecontainer--prison {
|
||||
min-height: 320px;
|
||||
}
|
||||
|
||||
.debtors-prison-visual {
|
||||
position: relative;
|
||||
width: min(100%, 540px);
|
||||
height: 320px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
background:
|
||||
radial-gradient(circle at 18% 22%, rgba(255, 233, 167, 0.95) 0, rgba(255, 233, 167, 0.95) 10%, rgba(255, 233, 167, 0) 11%),
|
||||
linear-gradient(180deg, #273149 0%, #31476b 42%, #6d5953 42%, #6d5953 100%);
|
||||
box-shadow: var(--shadow-soft);
|
||||
}
|
||||
|
||||
.debtors-prison-visual__moon {
|
||||
position: absolute;
|
||||
top: 42px;
|
||||
left: 72px;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 241, 194, 0.9);
|
||||
box-shadow: 0 0 24px rgba(255, 241, 194, 0.5);
|
||||
}
|
||||
|
||||
.debtors-prison-visual__tower {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 54px;
|
||||
width: 160px;
|
||||
height: 190px;
|
||||
transform: translateX(-50%);
|
||||
border-radius: 18px 18px 10px 10px;
|
||||
background:
|
||||
linear-gradient(180deg, #8c8a86 0%, #6f6a64 100%);
|
||||
box-shadow: inset 0 0 0 2px rgba(53, 49, 45, 0.18);
|
||||
}
|
||||
|
||||
.debtors-prison-visual__tower::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -26px;
|
||||
left: 18px;
|
||||
width: 124px;
|
||||
height: 34px;
|
||||
border-radius: 10px 10px 0 0;
|
||||
background:
|
||||
repeating-linear-gradient(90deg, #7d786f 0, #7d786f 18px, #646057 18px, #646057 26px);
|
||||
}
|
||||
|
||||
.debtors-prison-visual__tower::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 0;
|
||||
width: 42px;
|
||||
height: 88px;
|
||||
transform: translateX(-50%);
|
||||
border-radius: 18px 18px 0 0;
|
||||
background: #40362f;
|
||||
box-shadow: inset 0 0 0 2px rgba(17, 13, 11, 0.22);
|
||||
}
|
||||
|
||||
.debtors-prison-visual__bars {
|
||||
position: absolute;
|
||||
top: 116px;
|
||||
width: 34px;
|
||||
height: 54px;
|
||||
border-radius: 8px;
|
||||
background:
|
||||
repeating-linear-gradient(90deg, rgba(33, 31, 29, 0.85) 0, rgba(33, 31, 29, 0.85) 5px, transparent 5px, transparent 11px);
|
||||
}
|
||||
|
||||
.debtors-prison-visual__bars--left {
|
||||
left: calc(50% - 54px);
|
||||
}
|
||||
|
||||
.debtors-prison-visual__bars--right {
|
||||
right: calc(50% - 54px);
|
||||
}
|
||||
|
||||
.debtors-prison-visual__ground {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 64px;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(72, 57, 51, 0.2), rgba(72, 57, 51, 0.42)),
|
||||
repeating-linear-gradient(90deg, #756357 0, #756357 18px, #6d5a4f 18px, #6d5a4f 30px);
|
||||
}
|
||||
|
||||
.avatar {
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
|
||||
Reference in New Issue
Block a user