backend-lehrer (5 files): - alerts_agent/db/repository.py (992 → 5), abitur_docs_api.py (956 → 3) - teacher_dashboard_api.py (951 → 3), services/pdf_service.py (916 → 3) - mail/mail_db.py (987 → 6) klausur-service (5 files): - legal_templates_ingestion.py (942 → 3), ocr_pipeline_postprocess.py (929 → 4) - ocr_pipeline_words.py (876 → 3), ocr_pipeline_ocr_merge.py (616 → 2) - KorrekturPage.tsx (956 → 6) website (5 pages): - mail (985 → 9), edu-search (958 → 8), mac-mini (950 → 7) - ocr-labeling (946 → 7), audit-workspace (871 → 4) studio-v2 (5 files + 1 deleted): - page.tsx (946 → 5), MessagesContext.tsx (925 → 4) - korrektur (914 → 6), worksheet-cleanup (899 → 6) - useVocabWorksheet.ts (888 → 3) - Deleted dead page-original.tsx (934 LOC) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
137 lines
6.5 KiB
TypeScript
137 lines
6.5 KiB
TypeScript
'use client'
|
|
|
|
import { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react'
|
|
import type { Contact, Conversation, Message, MessageTemplate, MessagesStats, MessagesContextType } from './messages/types'
|
|
import { mockContacts, mockConversations, mockMessages, mockTemplates } from './messages/mock-data'
|
|
|
|
// Re-export types and helpers for backward compatibility
|
|
export type { Contact, Conversation, Message, MessageTemplate, MessagesStats } from './messages/types'
|
|
export { formatMessageTime, formatMessageDate, getContactInitials, getRoleLabel, getRoleColor, emojiCategories } from './messages/helpers'
|
|
|
|
const MessagesContext = createContext<MessagesContextType | null>(null)
|
|
|
|
export function MessagesProvider({ children }: { children: ReactNode }) {
|
|
const [contacts, setContacts] = useState<Contact[]>(mockContacts)
|
|
const [conversations, setConversations] = useState<Conversation[]>(mockConversations)
|
|
const [messages, setMessages] = useState<Record<string, Message[]>>(mockMessages)
|
|
const [templates] = useState<MessageTemplate[]>(mockTemplates)
|
|
const [stats] = useState<MessagesStats>({
|
|
total_contacts: mockContacts.length,
|
|
total_conversations: mockConversations.length,
|
|
total_messages: Object.values(mockMessages).flat().length,
|
|
unread_messages: mockConversations.reduce((sum, c) => sum + c.unread_count, 0)
|
|
})
|
|
const [isLoading] = useState(false)
|
|
const [error] = useState<string | null>(null)
|
|
const [currentConversationId, setCurrentConversationId] = useState<string | null>(null)
|
|
const [mounted, setMounted] = useState(false)
|
|
|
|
useEffect(() => { setMounted(true) }, [])
|
|
|
|
const unreadCount = conversations.reduce((sum, c) => sum + c.unread_count, 0)
|
|
|
|
const recentConversations = [...conversations].sort((a, b) => {
|
|
if (a.pinned && !b.pinned) return -1
|
|
if (!a.pinned && b.pinned) return 1
|
|
const aTime = a.last_message_time ? new Date(a.last_message_time).getTime() : 0
|
|
const bTime = b.last_message_time ? new Date(b.last_message_time).getTime() : 0
|
|
return bTime - aTime
|
|
})
|
|
|
|
const fetchContacts = useCallback(async () => { setContacts(mockContacts) }, [])
|
|
const fetchConversations = useCallback(async () => { setConversations(mockConversations) }, [])
|
|
const fetchMessages = useCallback(async (conversationId: string): Promise<Message[]> => {
|
|
return messages[conversationId] || []
|
|
}, [messages])
|
|
|
|
const sendMessage = useCallback(async (
|
|
conversationId: string, content: string, sendEmail = false, replyTo?: string
|
|
): Promise<Message | null> => {
|
|
const newMsg: Message = {
|
|
id: `msg_${Date.now()}`, conversation_id: conversationId, sender_id: 'self',
|
|
content, content_type: 'text', timestamp: new Date().toISOString(),
|
|
read: true, delivered: true, send_email: sendEmail, email_sent: sendEmail, reply_to: replyTo
|
|
}
|
|
setMessages(prev => ({ ...prev, [conversationId]: [...(prev[conversationId] || []), newMsg] }))
|
|
setConversations(prev => prev.map(c =>
|
|
c.id === conversationId
|
|
? { ...c, last_message: content.length > 50 ? content.slice(0, 50) + '...' : content,
|
|
last_message_time: newMsg.timestamp, updated_at: newMsg.timestamp }
|
|
: c
|
|
))
|
|
return newMsg
|
|
}, [])
|
|
|
|
const markAsRead = useCallback(async (conversationId: string) => {
|
|
setMessages(prev => ({ ...prev, [conversationId]: (prev[conversationId] || []).map(m => ({ ...m, read: true })) }))
|
|
setConversations(prev => prev.map(c => c.id === conversationId ? { ...c, unread_count: 0 } : c))
|
|
}, [])
|
|
|
|
const createConversation = useCallback(async (contactId: string): Promise<Conversation | null> => {
|
|
const existing = conversations.find(c => !c.is_group && c.participant_ids.includes(contactId))
|
|
if (existing) return existing
|
|
const contact = contacts.find(c => c.id === contactId)
|
|
const newConv: Conversation = {
|
|
id: `conv_${Date.now()}`, participant_ids: [contactId],
|
|
created_at: new Date().toISOString(), updated_at: new Date().toISOString(),
|
|
unread_count: 0, is_group: false, title: contact?.name || 'Neue Konversation'
|
|
}
|
|
setConversations(prev => [newConv, ...prev])
|
|
setMessages(prev => ({ ...prev, [newConv.id]: [] }))
|
|
return newConv
|
|
}, [conversations, contacts])
|
|
|
|
const addReaction = useCallback((messageId: string, emoji: string) => {
|
|
setMessages(prev => {
|
|
const newMessages = { ...prev }
|
|
for (const convId of Object.keys(newMessages)) {
|
|
newMessages[convId] = newMessages[convId].map(msg => {
|
|
if (msg.id !== messageId) return msg
|
|
const reactions = [...(msg.reactions || [])]
|
|
const existingIndex = reactions.findIndex(r => r.user_id === 'self')
|
|
if (existingIndex >= 0) {
|
|
if (reactions[existingIndex].emoji === emoji) { reactions.splice(existingIndex, 1) }
|
|
else { reactions[existingIndex] = { ...reactions[existingIndex], emoji } }
|
|
} else { reactions.push({ emoji, user_id: 'self' }) }
|
|
return { ...msg, reactions }
|
|
})
|
|
}
|
|
return newMessages
|
|
})
|
|
}, [])
|
|
|
|
const deleteMessage = useCallback((conversationId: string, messageId: string) => {
|
|
setMessages(prev => ({ ...prev, [conversationId]: (prev[conversationId] || []).filter(m => m.id !== messageId) }))
|
|
}, [])
|
|
|
|
const pinConversation = useCallback((conversationId: string) => {
|
|
setConversations(prev => prev.map(c => c.id === conversationId ? { ...c, pinned: !c.pinned } : c))
|
|
}, [])
|
|
|
|
const muteConversation = useCallback((conversationId: string) => {
|
|
setConversations(prev => prev.map(c => c.id === conversationId ? { ...c, muted: !c.muted } : c))
|
|
}, [])
|
|
|
|
const value: MessagesContextType = {
|
|
contacts: mounted ? contacts : [], conversations: mounted ? conversations : [],
|
|
messages: mounted ? messages : {}, templates: mounted ? templates : [],
|
|
stats: mounted ? stats : { total_contacts: 0, total_conversations: 0, total_messages: 0, unread_messages: 0 },
|
|
unreadCount: mounted ? unreadCount : 0,
|
|
recentConversations: mounted ? recentConversations : [],
|
|
fetchContacts, fetchConversations, fetchMessages, sendMessage, markAsRead,
|
|
createConversation, addReaction, deleteMessage, pinConversation, muteConversation,
|
|
isLoading, error, currentConversationId,
|
|
setCurrentConversationId: mounted ? setCurrentConversationId : () => {}
|
|
}
|
|
|
|
return <MessagesContext.Provider value={value}>{children}</MessagesContext.Provider>
|
|
}
|
|
|
|
export function useMessages() {
|
|
const context = useContext(MessagesContext)
|
|
if (!context) {
|
|
throw new Error('useMessages must be used within a MessagesProvider')
|
|
}
|
|
return context
|
|
}
|