Rechtliches: Anzeige-Texte der Vertragsliste als Lib mit Test
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
625efc7f18
commit
313ead77f4
2 files changed
+96
No files matched your search
@@ -0,0 +1,53 @@
|
|||||||
|
// Anzeige-Texte der Vertragsliste im Bereich Rechtliches (reine Funktionen, ohne React).
|
||||||
|
// Die Rechenlogik der Fristen steht in legal.js; hier nur Beschriftung, Badge und Daempfung.
|
||||||
|
import { deadlineTone, formatDate } from './legal'
|
||||||
|
|
||||||
|
const euro = new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' })
|
||||||
|
|
||||||
|
export function costLabel(c) {
|
||||||
|
if (c.cost === null || c.cost === undefined) return '–'
|
||||||
|
return `${euro.format(c.cost)}/${c.costInterval === 'year' ? 'Jahr' : 'Monat'}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function termLabel(c) {
|
||||||
|
if (c.termMonths === null || c.termMonths === undefined) return '–'
|
||||||
|
if (c.termMonths === 0) return 'jederzeit kündbar'
|
||||||
|
const renew = c.renewalMonths ? `, dann je ${c.renewalMonths} Monate` : ', endet danach'
|
||||||
|
return `${c.termMonths} Monate${renew}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function noticeLabel(c) {
|
||||||
|
if (c.noticeValue === null || c.noticeValue === undefined) return '–'
|
||||||
|
return `${c.noticeValue} ${c.noticeUnit === 'days' ? 'Tage' : 'Monate'}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deadlineLabel(r) {
|
||||||
|
switch (r.state) {
|
||||||
|
case 'active': return `kündbar bis ${formatDate(r.noticeBy)} (Periode endet ${formatDate(r.periodEnd)})`
|
||||||
|
case 'anytime': return 'jederzeit kündbar'
|
||||||
|
case 'ends': return `endet am ${formatDate(r.periodEnd)}`
|
||||||
|
case 'ended': return `beendet seit ${formatDate(r.periodEnd)}`
|
||||||
|
case 'cancelled': return r.periodEnd ? `gekündigt zum ${formatDate(r.periodEnd)}` : 'gekündigt'
|
||||||
|
default: return 'Fristen fehlen'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Darstellung der Frist-Spalte: text (voller Satz), lead und rest (Aufteilung, wenn ein
|
||||||
|
* Badge gesetzt ist, sonst lead = text und rest leer), badge ('danger' bei rot, 'warn' bei
|
||||||
|
* orange, sonst null) und muted (Vertrag beendet, gekuendigt oder unvollstaendig).
|
||||||
|
*/
|
||||||
|
export function deadlineView(result, today) {
|
||||||
|
const tone = deadlineTone(result, today)
|
||||||
|
const text = deadlineLabel(result)
|
||||||
|
const badge = tone === 'red' ? 'danger' : tone === 'orange' ? 'warn' : null
|
||||||
|
const muted = tone === 'grey' || tone === 'missing'
|
||||||
|
if (!badge) return { text, lead: text, rest: '', badge, muted }
|
||||||
|
return {
|
||||||
|
text,
|
||||||
|
lead: `kündbar bis ${formatDate(result.noticeBy)}`,
|
||||||
|
rest: `(Periode endet ${formatDate(result.periodEnd)})`,
|
||||||
|
badge,
|
||||||
|
muted,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import test from 'node:test'
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { loadModule } from './load.mjs'
|
||||||
|
|
||||||
|
const { costLabel, termLabel, noticeLabel, deadlineView } = await loadModule('src/lib/legalView.js')
|
||||||
|
const NB = ' '
|
||||||
|
const today = '2026-09-30'
|
||||||
|
const active = (noticeBy, periodEnd = '2027-03-12') => ({ state: 'active', noticeBy, periodEnd })
|
||||||
|
const v = (r) => deadlineView(r, today)
|
||||||
|
|
||||||
|
test('Vertragsliste: Kosten, Laufzeit, Frist wie bisher', () => {
|
||||||
|
assert.equal(costLabel({ cost: null }), '–')
|
||||||
|
assert.equal(costLabel({ cost: 12.5, costInterval: 'month' }), `12,50${NB}€/Monat`)
|
||||||
|
assert.equal(costLabel({ cost: 120, costInterval: 'year' }), `120,00${NB}€/Jahr`)
|
||||||
|
assert.equal(termLabel({}), '–')
|
||||||
|
assert.equal(termLabel({ termMonths: 0 }), 'jederzeit kündbar')
|
||||||
|
assert.equal(termLabel({ termMonths: 12, renewalMonths: 12 }), '12 Monate, dann je 12 Monate')
|
||||||
|
assert.equal(termLabel({ termMonths: 24, renewalMonths: 0 }), '24 Monate, endet danach')
|
||||||
|
assert.equal(noticeLabel({}), '–')
|
||||||
|
assert.equal(noticeLabel({ noticeValue: 1, noticeUnit: 'months' }), '1 Monate')
|
||||||
|
assert.equal(noticeLabel({ noticeValue: 14, noticeUnit: 'days' }), '14 Tage')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('deadlineView: Badge nur bei rot und orange, Text wörtlich wie bisher', () => {
|
||||||
|
assert.deepEqual(v(active('2026-10-15')), {
|
||||||
|
text: 'kündbar bis 15.10.2026 (Periode endet 12.03.2027)',
|
||||||
|
lead: 'kündbar bis 15.10.2026', rest: '(Periode endet 12.03.2027)', badge: 'danger', muted: false,
|
||||||
|
})
|
||||||
|
assert.equal(v(active('2026-10-30')).badge, 'danger') // 30 Tage
|
||||||
|
assert.equal(v(active('2026-10-31')).badge, 'warn') // 31 Tage
|
||||||
|
assert.equal(v(active('2026-12-29')).badge, 'warn') // 90 Tage
|
||||||
|
const normal = v(active('2026-12-30')) // 91 Tage
|
||||||
|
assert.deepEqual([normal.badge, normal.lead === normal.text, normal.rest], [null, true, ''])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('deadlineView: Text und Dämpfung der übrigen Zustände', () => {
|
||||||
|
assert.deepEqual([v({ state: 'anytime' }).text, v({ state: 'anytime' }).muted], ['jederzeit kündbar', false])
|
||||||
|
assert.equal(v({ state: 'ends', periodEnd: '2027-01-31' }).text, 'endet am 31.01.2027')
|
||||||
|
assert.deepEqual([v({ state: 'ended', periodEnd: '2026-01-31' }).text, v({ state: 'ended', periodEnd: '2026-01-31' }).muted], ['beendet seit 31.01.2026', true])
|
||||||
|
assert.equal(v({ state: 'cancelled', periodEnd: '2027-01-31' }).text, 'gekündigt zum 31.01.2027')
|
||||||
|
assert.equal(v({ state: 'cancelled', periodEnd: null }).text, 'gekündigt')
|
||||||
|
assert.deepEqual([v({ state: 'incomplete' }).text, v({ state: 'incomplete' }).muted], ['Fristen fehlen', true])
|
||||||
|
})
|
||||||
Reference in new issue
Block a user