Schulportal-Konventionen übernommen: Dark Mode (ThemeToggle), beispieldaten.py + generated.json, kimi.md, mail-server (Postfix-Relay + API), Deploy-Workflow (pscp/plink, npm_default), Screenshots in preview/

This commit is contained in:
2026-10-02 10:46:10 +02:00
parent ca97ea028f
commit b3dfb8ab7c
27 changed files with 7284 additions and 163 deletions
File diff suppressed because it is too large Load Diff
+4 -86
View File
@@ -6,97 +6,15 @@ import type {
Thread,
Announcement,
} from '../types'
const buildings = [
{ name: 'Hauptstraße 12', prefix: 'HS', count: 20, baseRent: 9.8 },
{ name: 'Ringweg 3', prefix: 'RW', count: 15, baseRent: 8.9 },
{ name: 'Am Park 5', prefix: 'AP', count: 15, baseRent: 10.4 },
]
const firstNames = [
'Anna', 'Jonas', 'Marta', 'Leon', 'Sofia', 'Felix', 'Lena', 'Paul',
'Emma', 'Noah', 'Clara', 'Ben', 'Nora', 'Max', 'Julia', 'Tim',
'Sarah', 'Finn', 'Laura', 'Jan', 'Mia', 'Tom', 'Ela', 'Kim',
]
const lastNames = [
'Berger', 'Klein', 'Wolf', 'Neumann', 'Schäfer', 'Krüger', 'Brandt',
'Vogel', 'Otto', 'Horn', 'Busch', 'Thomas', 'Pohl', 'Sander', 'Lindner',
]
import generated from './generated.json'
// Demo-Mieter: Whg. Hauptstraße 12 Nr. 7
export const DEMO_TENANT = 'Anna Berger'
export const DEMO_UNIT_ID = 'hs-7'
let nameIdx = 0
function nextName(): string {
const n = `${firstNames[nameIdx % firstNames.length]} ${lastNames[nameIdx % lastNames.length]}`
nameIdx++
return n
}
export const units: Unit[] = buildings.flatMap((b) =>
Array.from({ length: b.count }, (_, i) => {
const num = i + 1
const size = 38 + ((num * 7 + b.prefix.length * 3) % 78) // 38–115 m²
const coldRent = Math.round(size * b.baseRent)
const warmRent = coldRent + Math.round(2.4 * size) + (num % 3 === 0 ? 35 : 0)
const floor = num <= 2 ? 0 : Math.min(4, Math.floor((num - 1) / 4))
const occupied = num % 9 !== 0 // ~89 % Vermietung -> 50 Wohnungen, ~5 frei
const tenant =
b.prefix === 'HS' && num === 7
? DEMO_TENANT
: occupied
? nextName()
: null
return {
id: `${b.prefix.toLowerCase()}-${num}`,
building: b.name,
number: `${num}`,
floor,
size,
coldRent,
warmRent,
tenant,
status: occupied ? ('vermietet' as const) : ('frei' as const),
}
}),
)
const monthNames = [
'Januar', 'Februar', 'März', 'April', 'Mai', 'Juni',
'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember',
]
export const transactions: Transaction[] = units
.filter((u) => u.status === 'vermietet')
.flatMap((u) => {
const txs: Transaction[] = []
for (let m = 0; m < 12; m++) {
const dueMonth = m + 1
txs.push({
id: `tx-${u.id}-${m}`,
unitId: u.id,
tenant: u.tenant!,
month: monthNames[m],
year: 2026,
amount: u.warmRent,
type: 'Miete',
status:
dueMonth <= 9
? (u.id.charCodeAt(3) + m) % 11 === 0
? 'überfällig'
: 'bezahlt'
: dueMonth === 10
? 'offen'
: 'offen',
paidAt:
dueMonth <= 9 && (u.id.charCodeAt(3) + m) % 11 !== 0
? `0${(m % 8) + 1}.0${dueMonth}.2026`
: undefined,
})
}
return txs
})
// Maschinell erzeugte Bestandsdaten (beispieldaten.py -> generated.json)
export const units = generated.units as Unit[]
export const transactions = generated.transactions as Transaction[]
export const tickets: Ticket[] = [
{