v1.2: E-Mail-Benachrichtigungen an Portal angeschlossen
- nginx-Proxy POST /api/mail/send -> mp-mail-api:8080 (nur POST, 64k Limit) - Token serverseitig via envsubst-Template + portal.env (gitignored, nur Server) - src/lib/mail.ts: fire-and-forget Versand, Empfaenger-Aufloesung (Verwaltung/Mieter) - Benachrichtigung: neue Schadensmeldung -> Verwaltung, Nachrichten/Antworten -> Gegenstelle - E2E live verifiziert (versandt: true an axel@hocks.eu), GET 403 - Doku (kimi.md, README) nachgefuehrt
This commit is contained in:
+52
-7
@@ -13,9 +13,30 @@ import {
|
||||
} from '@/components/ui/dialog'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { useStore } from '../store/store'
|
||||
import {
|
||||
sendeBenachrichtigung,
|
||||
verwaltungsAdressen,
|
||||
mieterAdressenFuerEinheit,
|
||||
nachrichtenText,
|
||||
} from '../lib/mail'
|
||||
|
||||
/** Gegenstelle benachrichtigen: Mieter → Verwaltung, Verwaltung → Mieter der Wohnung. */
|
||||
function benachrichtigeGegenstelle(opts: {
|
||||
benutzer: ReturnType<typeof useStore>['benutzer']
|
||||
vonRolle: 'tenant' | 'admin'
|
||||
einheitId: string
|
||||
betreff: string
|
||||
text: string
|
||||
}) {
|
||||
const adressen =
|
||||
opts.vonRolle === 'tenant'
|
||||
? verwaltungsAdressen(opts.benutzer)
|
||||
: mieterAdressenFuerEinheit(opts.benutzer, opts.einheitId)
|
||||
sendeBenachrichtigung(adressen, opts.betreff, nachrichtenText(opts.text))
|
||||
}
|
||||
|
||||
function NewMessageDialog({ onCreated }: { onCreated: (id: string) => void }) {
|
||||
const { dispatch, role, me, meineEinheit } = useStore()
|
||||
const { dispatch, role, me, meineEinheit, benutzer } = useStore()
|
||||
const [open, setOpen] = useState(false)
|
||||
const [subject, setSubject] = useState('')
|
||||
const [text, setText] = useState('')
|
||||
@@ -25,12 +46,14 @@ function NewMessageDialog({ onCreated }: { onCreated: (id: string) => void }) {
|
||||
const submit = () => {
|
||||
if (!subject.trim() || !text.trim()) return
|
||||
const id = `m-${Date.now()}`
|
||||
const unitId = role === 'tenant' ? meineEinheit.id : 'hs-1'
|
||||
const autor = role === 'tenant' ? (me?.name ?? 'Mieter') : verwaltungsName
|
||||
dispatch({
|
||||
type: 'newThread',
|
||||
thread: {
|
||||
id,
|
||||
unitId: role === 'tenant' ? meineEinheit.id : 'hs-1',
|
||||
tenant: role === 'tenant' ? (me?.name ?? 'Mieter') : verwaltungsName,
|
||||
unitId,
|
||||
tenant: autor,
|
||||
subject: subject.trim(),
|
||||
unreadByAdmin: true,
|
||||
unreadByTenant: false,
|
||||
@@ -38,13 +61,23 @@ function NewMessageDialog({ onCreated }: { onCreated: (id: string) => void }) {
|
||||
{
|
||||
id: `${id}-1`,
|
||||
from: role,
|
||||
author: role === 'tenant' ? (me?.name ?? 'Mieter') : verwaltungsName,
|
||||
author: autor,
|
||||
text: text.trim(),
|
||||
at: new Date().toLocaleString('de-DE'),
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
benachrichtigeGegenstelle({
|
||||
benutzer,
|
||||
vonRolle: role,
|
||||
einheitId: unitId,
|
||||
betreff:
|
||||
role === 'tenant'
|
||||
? `Neue Nachricht im Mieterportal: ${subject.trim()}`
|
||||
: `Nachricht der Verwaltung: ${subject.trim()}`,
|
||||
text: `Neue Unterhaltung von ${autor}:\n\n„${text.trim()}“`,
|
||||
})
|
||||
setSubject('')
|
||||
setText('')
|
||||
setOpen(false)
|
||||
@@ -84,7 +117,7 @@ function NewMessageDialog({ onCreated }: { onCreated: (id: string) => void }) {
|
||||
}
|
||||
|
||||
export default function Messages() {
|
||||
const { threads, role, dispatch, units, me, meineEinheit } = useStore()
|
||||
const { threads, role, dispatch, units, me, meineEinheit, benutzer } = useStore()
|
||||
const meinName = me?.name ?? 'Mieter'
|
||||
const verwaltungsName = 'Joseph Hinterstein'
|
||||
const visible = role === 'tenant'
|
||||
@@ -103,17 +136,29 @@ export default function Messages() {
|
||||
|
||||
const send = () => {
|
||||
if (!active || !draft.trim()) return
|
||||
const text = draft.trim()
|
||||
const autor = role === 'tenant' ? meinName : verwaltungsName
|
||||
dispatch({
|
||||
type: 'addMessage',
|
||||
threadId: active.id,
|
||||
message: {
|
||||
id: `${active.id}-${Date.now()}`,
|
||||
from: role,
|
||||
author: role === 'tenant' ? meinName : verwaltungsName,
|
||||
text: draft.trim(),
|
||||
author: autor,
|
||||
text,
|
||||
at: new Date().toLocaleString('de-DE'),
|
||||
},
|
||||
})
|
||||
benachrichtigeGegenstelle({
|
||||
benutzer,
|
||||
vonRolle: role,
|
||||
einheitId: active.unitId,
|
||||
betreff:
|
||||
role === 'tenant'
|
||||
? `Neue Antwort im Mieterportal: ${active.subject}`
|
||||
: `Antwort der Verwaltung: ${active.subject}`,
|
||||
text: `Neue Nachricht von ${autor} in der Unterhaltung „${active.subject}“:\n\n„${text}“`,
|
||||
})
|
||||
setDraft('')
|
||||
}
|
||||
|
||||
|
||||
+25
-3
@@ -13,6 +13,12 @@ import {
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import {
|
||||
sendeBenachrichtigung,
|
||||
verwaltungsAdressen,
|
||||
nachrichtenText,
|
||||
einheitLabel,
|
||||
} from '../lib/mail'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -46,7 +52,7 @@ const categories = [
|
||||
]
|
||||
|
||||
function NewTicketDialog() {
|
||||
const { dispatch, role, me, meineEinheit } = useStore()
|
||||
const { dispatch, role, me, meineEinheit, benutzer, units } = useStore()
|
||||
const [open, setOpen] = useState(false)
|
||||
const [title, setTitle] = useState('')
|
||||
const [category, setCategory] = useState('Sanitär')
|
||||
@@ -55,12 +61,15 @@ function NewTicketDialog() {
|
||||
|
||||
const submit = () => {
|
||||
if (!title.trim()) return
|
||||
const unitId = role === 'tenant' ? meineEinheit.id : 'hs-1'
|
||||
const einheit = units.find((u) => u.id === unitId) ?? meineEinheit
|
||||
const mieterName = role === 'tenant' ? (me?.name ?? 'Mieter') : 'Hinterstein Immobilien (intern)'
|
||||
dispatch({
|
||||
type: 'addTicket',
|
||||
ticket: {
|
||||
id: `t-${Date.now()}`,
|
||||
unitId: role === 'tenant' ? meineEinheit.id : 'hs-1',
|
||||
tenant: role === 'tenant' ? (me?.name ?? 'Mieter') : 'Hinterstein Immobilien (intern)',
|
||||
unitId,
|
||||
tenant: mieterName,
|
||||
title: title.trim(),
|
||||
description: description.trim() || '–',
|
||||
category,
|
||||
@@ -69,6 +78,19 @@ function NewTicketDialog() {
|
||||
createdAt: new Date().toLocaleDateString('de-DE'),
|
||||
},
|
||||
})
|
||||
// Verwaltung per E-Mail benachrichtigen (fire-and-forget)
|
||||
sendeBenachrichtigung(
|
||||
verwaltungsAdressen(benutzer),
|
||||
`Neue Schadensmeldung: ${title.trim()} (${einheitLabel(einheit)})`,
|
||||
nachrichtenText(
|
||||
`Im Mieterportal wurde eine neue Schadensmeldung erstellt.\n\n` +
|
||||
`Wohnung: ${einheitLabel(einheit)}\n` +
|
||||
`Meldung von: ${mieterName}\n` +
|
||||
`Kategorie: ${category}\n` +
|
||||
`Dringlichkeit: ${priority}\n\n` +
|
||||
`${title.trim()}\n${description.trim() || '–'}`,
|
||||
),
|
||||
)
|
||||
setTitle('')
|
||||
setDescription('')
|
||||
setOpen(false)
|
||||
|
||||
Reference in New Issue
Block a user