updated chat

This commit is contained in:
Linrador 2026-06-15 08:27:12 +02:00
parent b3c07355a8
commit 04ab4523a6
7 changed files with 116 additions and 15 deletions

View File

@ -1,7 +1,7 @@
PORT=8080
DATABASE_URL=postgres://postgres:Timmy0104199%3F@localhost:5432/teg?sslmode=disable
JWT_SECRET=tegvideo7010!
FRONTEND_ORIGIN=http://10.0.1.25:5173
FRONTEND_ORIGIN=http://localhost:5173
ADDRESS_SEARCH_PROVIDER=photon
ADDRESS_SEARCH_URL=https://photon.komoot.io/api
ADDRESS_SEARCH_LANGUAGE=de
@ -11,7 +11,7 @@ ADDRESS_SEARCH_USER_AGENT=TEG-App/1.0
ADMIN_USERNAME=admin
ADMIN_DISPLAY_NAME=Administrator
ADMIN_EMAIL=admin@example.com
ADMIN_EMAIL=admin@tegdssd.de
ADMIN_UNIT=Administration
ADMIN_GROUP=admin
ADMIN_RIGHTS=admin,users:read,users:write

View File

@ -352,10 +352,15 @@ func (s *Server) publishChatMessage(
}
for _, recipientID := range recipientIDs {
notificationType := "chat.message"
if conversationType == "channel" {
notificationType = "channel.message"
}
_ = s.createAndPublishNotification(
ctx,
recipientID,
"chat.message",
notificationType,
title,
preview,
"chat",

View File

@ -22,6 +22,7 @@ type notificationPreferencesResponse struct {
DeviceJournals bool `json:"deviceJournals"`
SystemMessages bool `json:"systemMessages"`
ChatMessages bool `json:"chatMessages"`
ChannelMessages bool `json:"channelMessages"`
DesktopEnabled bool `json:"desktopEnabled"`
}
@ -37,6 +38,7 @@ func defaultNotificationPreferences() notificationPreferencesResponse {
DeviceJournals: true,
SystemMessages: true,
ChatMessages: true,
ChannelMessages: true,
DesktopEnabled: false,
}
}
@ -108,9 +110,10 @@ func (s *Server) handleUpdateNotificationPreferences(w http.ResponseWriter, r *h
device_journals,
system_messages,
chat_messages,
channel_messages,
desktop_enabled
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
ON CONFLICT (user_id)
DO UPDATE SET
in_app_enabled = EXCLUDED.in_app_enabled,
@ -123,6 +126,7 @@ func (s *Server) handleUpdateNotificationPreferences(w http.ResponseWriter, r *h
device_journals = EXCLUDED.device_journals,
system_messages = EXCLUDED.system_messages,
chat_messages = EXCLUDED.chat_messages,
channel_messages = EXCLUDED.channel_messages,
desktop_enabled = EXCLUDED.desktop_enabled,
updated_at = now()
`,
@ -137,6 +141,7 @@ func (s *Server) handleUpdateNotificationPreferences(w http.ResponseWriter, r *h
input.DeviceJournals,
input.SystemMessages,
input.ChatMessages,
input.ChannelMessages,
input.DesktopEnabled,
)
if err != nil {
@ -172,6 +177,7 @@ func (s *Server) getNotificationPreferences(ctx context.Context, userID string)
device_journals,
system_messages,
chat_messages,
channel_messages,
desktop_enabled
FROM notification_preferences
WHERE user_id = $1
@ -189,6 +195,7 @@ func (s *Server) getNotificationPreferences(ctx context.Context, userID string)
&settings.DeviceJournals,
&settings.SystemMessages,
&settings.ChatMessages,
&settings.ChannelMessages,
&settings.DesktopEnabled,
)

View File

@ -98,6 +98,9 @@ func shouldDeliverNotification(
entityType string,
) bool {
switch {
case notificationType == "channel.message":
return preferences.ChannelMessages
case strings.HasPrefix(notificationType, "chat.") || entityType == "chat":
return preferences.ChatMessages
@ -228,7 +231,11 @@ func (s *Server) createAndPublishNotification(
notification.Desktop = preferences.DesktopEnabled
notification.InApp = preferences.InAppEnabled
if notification.EntityType == "chat" {
notification.InApp = preferences.ChatMessages
if notification.Type == "channel.message" {
notification.InApp = preferences.ChannelMessages
} else {
notification.InApp = preferences.ChatMessages
}
}
}

View File

@ -623,6 +623,7 @@ func createSchema(ctx context.Context, db *pgxpool.Pool) error {
device_journals BOOLEAN NOT NULL DEFAULT true,
system_messages BOOLEAN NOT NULL DEFAULT true,
chat_messages BOOLEAN NOT NULL DEFAULT true,
channel_messages BOOLEAN NOT NULL DEFAULT true,
desktop_enabled BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
@ -631,6 +632,7 @@ func createSchema(ctx context.Context, db *pgxpool.Pool) error {
`,
`ALTER TABLE IF EXISTS notification_preferences ADD COLUMN IF NOT EXISTS chat_messages BOOLEAN NOT NULL DEFAULT true`,
`ALTER TABLE IF EXISTS notification_preferences ADD COLUMN IF NOT EXISTS channel_messages BOOLEAN NOT NULL DEFAULT true`,
`ALTER TABLE IF EXISTS notification_preferences ADD COLUMN IF NOT EXISTS desktop_enabled BOOLEAN NOT NULL DEFAULT false`,
`

View File

@ -24,6 +24,7 @@ import {
} from '@heroicons/react/24/outline'
import { MapPinIcon as MapPinSolidIcon } from '@heroicons/react/24/solid'
import {
Fragment,
useCallback,
useEffect,
useMemo,
@ -291,6 +292,49 @@ function formatTime(value: string) {
}).format(new Date(value))
}
function isSameMessageDay(first: string, second: string) {
const firstDate = new Date(first)
const secondDate = new Date(second)
if (
Number.isNaN(firstDate.getTime()) ||
Number.isNaN(secondDate.getTime())
) {
return first === second
}
return (
firstDate.getFullYear() === secondDate.getFullYear() &&
firstDate.getMonth() === secondDate.getMonth() &&
firstDate.getDate() === secondDate.getDate()
)
}
function formatMessageDate(value: string) {
const date = new Date(value)
if (Number.isNaN(date.getTime())) {
return ''
}
const today = new Date()
const yesterday = new Date(today)
yesterday.setDate(today.getDate() - 1)
if (isSameMessageDay(value, today.toISOString())) {
return 'Heute'
}
if (isSameMessageDay(value, yesterday.toISOString())) {
return 'Gestern'
}
return new Intl.DateTimeFormat('de-DE', {
weekday: 'long',
day: '2-digit',
month: 'long',
year: date.getFullYear() === today.getFullYear() ? undefined : 'numeric',
}).format(date)
}
function formatConversationTime(value?: string | null) {
if (!value) {
return ''
@ -1879,17 +1923,41 @@ export default function ChatPage({ currentUser }: ChatPageProps) {
</div>
) : (
<div className="mx-auto max-w-3xl space-y-4">
{messages.map((message) => {
{messages.map((message, index) => {
const ownMessage = message.sender.id === currentUser.id
const previousMessage = messages[index - 1]
const showDateSeparator =
!previousMessage ||
!isSameMessageDay(
previousMessage.createdAt,
message.createdAt,
)
const dateLabel = showDateSeparator
? formatMessageDate(message.createdAt)
: ''
return (
<div
key={message.id}
className={classNames(
ownMessage ? 'justify-end' : 'justify-start',
'flex items-end gap-2',
<Fragment key={message.id}>
{showDateSeparator && (
<div
role="separator"
aria-label={dateLabel}
className="flex items-center gap-3 py-1"
>
<span className="h-px flex-1 bg-gray-200 dark:bg-white/10" />
<span className="rounded-full bg-white px-3 py-1 text-[0.6875rem] font-semibold text-gray-500 shadow-sm ring-1 ring-gray-200 dark:bg-gray-900 dark:text-gray-400 dark:ring-white/10">
{dateLabel}
</span>
<span className="h-px flex-1 bg-gray-200 dark:bg-white/10" />
</div>
)}
>
<div
className={classNames(
ownMessage ? 'justify-end' : 'justify-start',
'flex items-end gap-2',
)}
>
{!ownMessage && (
<Avatar
src={message.sender.avatar}
@ -2064,7 +2132,8 @@ export default function ChatPage({ currentUser }: ChatPageProps) {
</button>
</div>
</div>
</div>
</div>
</Fragment>
)
})}
{showDirectTypingIndicator && (

View File

@ -33,6 +33,7 @@ type NotificationSettings = {
deviceJournals: boolean
systemMessages: boolean
chatMessages: boolean
channelMessages: boolean
desktopEnabled: boolean
}
@ -47,6 +48,7 @@ const defaultSettings: NotificationSettings = {
deviceJournals: true,
systemMessages: true,
chatMessages: true,
channelMessages: true,
desktopEnabled: false,
}
@ -293,7 +295,7 @@ export default function Benachrichtigungen() {
updateSetting('desktopEnabled', true)
new Notification('TEG-Benachrichtigungen aktiviert', {
body: 'Neue Chatnachrichten können jetzt auf Windows-Ebene angezeigt werden.',
body: 'Neue Chat- und Channel-Nachrichten können jetzt auf Windows-Ebene angezeigt werden.',
})
}
@ -406,7 +408,7 @@ export default function Benachrichtigungen() {
<NotificationCard
icon={<EnvelopeIcon aria-hidden="true" className="size-5" />}
title="Windows-Benachrichtigungen"
description="Zeigt neue Chatnachrichten außerhalb des Browserfensters an."
description="Zeigt neue Chat- und Channel-Nachrichten außerhalb des Browserfensters an."
>
<Switch
checked={settings.desktopEnabled}
@ -449,6 +451,15 @@ export default function Benachrichtigungen() {
description="Zeigt eine Browser-Schnellantwort bei neuen Team- und Direktnachrichten."
/>
<Switch
checked={settings.channelMessages}
onChange={(event) =>
updateSetting('channelMessages', event.target.checked)
}
label="Channel-Nachrichten"
description="Zeigt eine Browser-Benachrichtigung bei neuen Meldungen aus Channels."
/>
<Switch
checked={settings.operationUpdates}
onChange={(event) =>