feat(vocab): stabilize daily batch handling in SRS session and improve due item display
All checks were successful
Deploy to production / deploy (push) Successful in 3m8s

This commit is contained in:
Torsten Schulz (local)
2026-09-16 10:13:24 +02:00
parent 20505f7545
commit 57a0e89156
8 changed files with 63 additions and 44 deletions

View File

@@ -133,7 +133,7 @@
</div>
<div>
<button type="button" @click="orderParty()">
<button type="button" @click="orderParty()" :disabled="isOrderingParty">
{{ $t('falukant.reputation.party.order') }}
</button>
</div>
@@ -224,6 +224,7 @@ export default {
selectedNobilityIds: [],
servantRatio: 50,
relationshipId: null,
isOrderingParty: false,
weddingCandidates: [],
inProgressParties: [],
completedParties: [],
@@ -317,15 +318,23 @@ export default {
this.nobilityTitles = await apiClient.get('/api/falukant/nobility/titels').then(r => r.data)
},
async orderParty() {
await apiClient.post('/api/falukant/party', {
partyTypeId: this.newPartyTypeId,
musicId: this.musicId,
banquetteId: this.banquetteId,
nobilityIds: this.selectedNobilityIds.map(n => n.id ?? n),
servantRatio: this.servantRatio,
relationshipId: this.relationshipId
});
this.toggleNewPartyView();
if (this.isOrderingParty) return;
this.isOrderingParty = true;
try {
await apiClient.post('/api/falukant/party', {
partyTypeId: this.newPartyTypeId,
musicId: this.musicId,
banquetteId: this.banquetteId,
nobilityIds: this.selectedNobilityIds.map(n => n.id ?? n),
servantRatio: this.servantRatio,
relationshipId: this.relationshipId
});
await this.loadParties();
this.relationshipId = null;
this.toggleNewPartyView();
} finally {
this.isOrderingParty = false;
}
},
getPartyDate(createdAt) {
// Feste finden 1 Tag nach der Bestellung statt

View File

@@ -317,11 +317,10 @@
<section class="dialog intensive-status-dialog" role="dialog" aria-modal="true" @click.stop>
<h3>{{ $t('socialnetwork.vocab.courses.courseFlowIntensiveStatusTitle') }}</h3>
<p class="intensive-status-dialog__hint">{{ $t('socialnetwork.vocab.courses.courseFlowIntensiveStatusNoDate') }}</p>
<p v-if="srsDailyCount === 0 && srsUpcoming.length === 0">{{ $t('socialnetwork.vocab.courses.courseFlowIntensiveStatusAllDone') }}</p>
<p v-if="srsDueCount === 0 && srsUpcoming.length === 0">{{ $t('socialnetwork.vocab.courses.courseFlowIntensiveStatusAllDone') }}</p>
<ul v-else class="intensive-status-dialog__list">
<li v-if="srsDailyCount > 0" class="intensive-status-dialog__item--due">
<span>{{ $t('socialnetwork.vocab.courses.courseFlowIntensiveStatusDue') }}</span>
<strong>{{ $t('socialnetwork.vocab.courses.courseFlowIntensiveStatusProgress', { count: srsDailyCount }) }}</strong>
<li v-if="srsDueCount > 0" class="intensive-status-dialog__item--due">
<span>{{ $t('socialnetwork.vocab.courses.courseFlowIntensiveStatusDue', { remaining: srsDailyCount, quota: srsDailyQuota, total: srsDueCount }) }}</span>
</li>
<li v-for="entry in srsUpcoming" :key="entry.date">
<span>{{ formatSrsDate(entry.date) }}</span>
@@ -397,6 +396,7 @@ export default {
srsDueTotal: 0,
srsDailyLimit: 50,
srsTodayRemaining: null,
srsTodayQuota: null,
srsUpcoming: [],
srsLoading: false,
showIntensiveStatusDialog: false,
@@ -449,9 +449,17 @@ export default {
return Array.isArray(this.srsDueItems) ? this.srsDueItems.length : 0;
},
srsDailyCount() {
// Der Server ist die maßgebliche Quelle für fällige Begriffe. Ein alter
// localStorage-Sitzungsstand darf eine neue oder erneut fällige
// Tageswiederholung nicht unsichtbar machen.
// A valid current-day session represents the fixed daily batch. Without
// one, the server's due count determines the first batch (max. 50).
if (Number.isFinite(Number(this.srsTodayRemaining))) {
return Math.max(0, Number(this.srsTodayRemaining));
}
return Math.min(this.srsDueCount, this.srsDailyLimit);
},
srsDailyQuota() {
if (Number.isFinite(Number(this.srsTodayQuota))) {
return Math.max(0, Number(this.srsTodayQuota));
}
return Math.min(this.srsDueCount, this.srsDailyLimit);
},
hardVocabCount() {
@@ -588,13 +596,18 @@ export default {
&& session.dateKey === this.getLocalDateKey();
if (!isCurrentSession) {
this.srsTodayRemaining = null;
this.srsTodayQuota = null;
return;
}
const total = Math.max(0, Number(session.initialTotalDue) || 0);
const done = new Set(Array.isArray(session.doneIds) ? session.doneIds : []).size;
const initialIds = new Set(Array.isArray(session.initialDueIds) ? session.initialDueIds : []);
const done = new Set((Array.isArray(session.doneIds) ? session.doneIds : [])
.filter((id) => initialIds.has(id))).size;
this.srsTodayRemaining = Math.max(0, total - done);
this.srsTodayQuota = total;
} catch (_) {
this.srsTodayRemaining = null;
this.srsTodayQuota = null;
}
},
async refreshHardVocabList() {