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>
|
||||
|
||||
Reference in New Issue
Block a user