Files
breakpilot-lehrer/studio-v2/app/meet/_components/useMeetPage.ts
Benjamin Admin 0b37c5e692 [split-required] Split website + studio-v2 monoliths (Phase 3 continued)
Website (14 monoliths split):
- compliance/page.tsx (1,519 → 9), docs/audit (1,262 → 20)
- quality (1,231 → 16), alerts (1,203 → 10), docs (1,202 → 11)
- i18n.ts (1,173 → 8 language files)
- unity-bridge (1,094 → 12), backlog (1,087 → 6)
- training (1,066 → 8), rag (1,063 → 8)
- Deleted index_original.ts (4,899 LOC dead backup)

Studio-v2 (5 monoliths split):
- meet/page.tsx (1,481 → 9), messages (1,166 → 9)
- AlertsB2BContext.tsx (1,165 → 5 modules)
- alerts-b2b/page.tsx (1,019 → 6), korrektur/archiv (1,001 → 6)

All existing imports preserved. Zero new TypeScript errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 17:52:36 +02:00

324 lines
11 KiB
TypeScript

'use client'
import { useState, useEffect } from 'react'
import {
type TabType,
type MeetingStats,
type Meeting,
type Recording,
type BreakoutRoom,
getBackendUrl,
getJitsiUrl,
} from './types'
export function useMeetPage() {
// Tab state
const [activeTab, setActiveTab] = useState<TabType>('dashboard')
const [stats, setStats] = useState<MeetingStats>({ active: 0, scheduled: 0, recordings: 0, participants: 0 })
const [scheduledMeetings, setScheduledMeetings] = useState<Meeting[]>([])
const [activeMeetings, setActiveMeetings] = useState<Meeting[]>([])
const [recordings, setRecordings] = useState<Recording[]>([])
const [recordingsFilter, setRecordingsFilter] = useState<string>('all')
const [loading, setLoading] = useState(true)
const [showNewMeetingModal, setShowNewMeetingModal] = useState(false)
const [showJoinModal, setShowJoinModal] = useState(false)
const [showTranscriptModal, setShowTranscriptModal] = useState(false)
const [currentMeetingUrl, setCurrentMeetingUrl] = useState('')
const [currentMeetingTitle, setCurrentMeetingTitle] = useState('')
const [currentRecording, setCurrentRecording] = useState<Recording | null>(null)
const [transcriptText, setTranscriptText] = useState('')
const [transcriptLoading, setTranscriptLoading] = useState(false)
// Breakout rooms state
const [breakoutRooms, setBreakoutRooms] = useState<BreakoutRoom[]>([
{ id: '1', name: 'Raum 1', participants: [] },
{ id: '2', name: 'Raum 2', participants: [] },
{ id: '3', name: 'Raum 3', participants: [] },
])
const [breakoutAssignment, setBreakoutAssignment] = useState('equal')
const [breakoutTimer, setBreakoutTimer] = useState(15)
const [hasActiveMeeting, setHasActiveMeeting] = useState(false)
// Form state
const [meetingType, setMeetingType] = useState('quick')
const [meetingTitle, setMeetingTitle] = useState('')
const [meetingDuration, setMeetingDuration] = useState(60)
const [meetingDateTime, setMeetingDateTime] = useState('')
const [enableLobby, setEnableLobby] = useState(true)
const [enableRecording, setEnableRecording] = useState(false)
const [muteOnStart, setMuteOnStart] = useState(true)
const [creating, setCreating] = useState(false)
const [errorMessage, setErrorMessage] = useState<string | null>(null)
// ============================================
// DATA FETCHING
// ============================================
useEffect(() => {
fetchData()
}, [])
const fetchData = async () => {
setLoading(true)
try {
const [statsRes, scheduledRes, activeRes, recordingsRes] = await Promise.all([
fetch(`${getBackendUrl()}/api/meetings/stats`).catch(() => null),
fetch(`${getBackendUrl()}/api/meetings/scheduled`).catch(() => null),
fetch(`${getBackendUrl()}/api/meetings/active`).catch(() => null),
fetch(`${getBackendUrl()}/api/recordings`).catch(() => null),
])
if (statsRes?.ok) {
const statsData = await statsRes.json()
setStats(statsData)
}
if (scheduledRes?.ok) {
const scheduledData = await scheduledRes.json()
setScheduledMeetings(scheduledData)
}
if (activeRes?.ok) {
const activeData = await activeRes.json()
setActiveMeetings(activeData)
setHasActiveMeeting(activeData.length > 0)
}
if (recordingsRes?.ok) {
const recordingsData = await recordingsRes.json()
setRecordings(recordingsData.recordings || recordingsData || [])
}
} catch (error) {
console.error('Failed to fetch meeting data:', error)
} finally {
setLoading(false)
}
}
// ============================================
// ACTIONS
// ============================================
const joinMeeting = (roomName: string, title: string) => {
const url = `${getJitsiUrl()}/${roomName}#config.prejoinPageEnabled=false&config.defaultLanguage=de&interfaceConfig.SHOW_JITSI_WATERMARK=false`
setCurrentMeetingUrl(url)
setCurrentMeetingTitle(title)
setShowJoinModal(true)
}
const createMeeting = async () => {
setCreating(true)
setErrorMessage(null)
try {
const response = await fetch(`${getBackendUrl()}/api/meetings/create`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: meetingType,
title: meetingTitle || 'Neues Meeting',
duration: meetingDuration,
scheduled_at: meetingType !== 'quick' ? meetingDateTime : null,
config: {
enable_lobby: enableLobby,
enable_recording: enableRecording,
start_with_audio_muted: muteOnStart,
start_with_video_muted: false,
},
}),
})
if (response.ok) {
const meeting = await response.json()
setShowNewMeetingModal(false)
if (meetingType === 'quick') {
joinMeeting(meeting.room_name, meetingTitle || 'Neues Meeting')
} else {
fetchData()
}
setMeetingTitle('')
setMeetingType('quick')
} else {
const errorData = await response.json().catch(() => ({}))
const errorMsg = errorData.detail || `Server-Fehler: ${response.status}`
setErrorMessage(errorMsg)
console.error('Failed to create meeting:', response.status, errorData)
}
} catch (error) {
const errorMsg = error instanceof Error ? error.message : 'Netzwerkfehler - Backend nicht erreichbar'
setErrorMessage(errorMsg)
console.error('Failed to create meeting:', error)
} finally {
setCreating(false)
}
}
const startQuickMeeting = async () => {
setCreating(true)
setErrorMessage(null)
try {
const response = await fetch(`${getBackendUrl()}/api/meetings/create`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: 'quick',
title: 'Sofort-Meeting',
duration: 60,
config: {
enable_lobby: false,
enable_recording: false,
start_with_audio_muted: true,
start_with_video_muted: false,
},
}),
})
if (response.ok) {
const meeting = await response.json()
joinMeeting(meeting.room_name, 'Sofort-Meeting')
} else {
const errorData = await response.json().catch(() => ({}))
const errorMsg = errorData.detail || `Server-Fehler: ${response.status}`
setErrorMessage(errorMsg)
console.error('Failed to create meeting:', response.status, errorData)
}
} catch (error) {
const errorMsg = error instanceof Error ? error.message : 'Netzwerkfehler - Backend nicht erreichbar'
setErrorMessage(errorMsg)
console.error('Failed to start quick meeting:', error)
} finally {
setCreating(false)
}
}
const openInNewTab = () => {
window.open(currentMeetingUrl, '_blank')
setShowJoinModal(false)
}
const copyMeetingLink = async (roomName: string) => {
const url = `${getJitsiUrl()}/${roomName}`
await navigator.clipboard.writeText(url)
}
// Recording actions
const playRecording = (recordingId: string) => {
window.open(`${getBackendUrl()}/meetings/recordings/${recordingId}/play`, '_blank')
}
const viewTranscript = async (recording: Recording) => {
setCurrentRecording(recording)
setShowTranscriptModal(true)
setTranscriptLoading(true)
setTranscriptText('')
try {
const response = await fetch(`${getBackendUrl()}/api/recordings/${recording.id}/transcription/text`)
if (response.ok) {
const data = await response.json()
setTranscriptText(data.text || 'Kein Transkript verfuegbar')
} else if (response.status === 404) {
setTranscriptText('PENDING')
} else {
setTranscriptText('Fehler beim Laden des Transkripts')
}
} catch {
setTranscriptText('Fehler beim Laden des Transkripts')
} finally {
setTranscriptLoading(false)
}
}
const startTranscription = async (recordingId: string) => {
try {
const response = await fetch(`${getBackendUrl()}/api/recordings/${recordingId}/transcribe`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ language: 'de', model: 'large-v3' }),
})
if (response.ok) {
alert('Transkription gestartet! Dies kann einige Minuten dauern.')
setShowTranscriptModal(false)
fetchData()
}
} catch {
alert('Fehler beim Starten der Transkription')
}
}
const downloadRecording = (recordingId: string) => {
window.location.href = `${getBackendUrl()}/api/recordings/${recordingId}/download`
}
const deleteRecording = async (recordingId: string) => {
const reason = prompt('Grund fuer die Loeschung (DSGVO-Dokumentation):')
if (!reason) return
try {
const response = await fetch(`${getBackendUrl()}/api/recordings/${recordingId}?reason=${encodeURIComponent(reason)}`, {
method: 'DELETE',
})
if (response.ok) {
fetchData()
}
} catch {
alert('Fehler beim Loeschen')
}
}
const filteredRecordings = recordings.filter((r) => {
if (recordingsFilter === 'all') return true
return r.status === recordingsFilter
})
const totalStorageBytes = recordings.reduce((sum, r) => sum + (r.file_size_bytes || 0), 0)
const maxStorageGB = 10
const storagePercent = ((totalStorageBytes / (maxStorageGB * 1024 * 1024 * 1024)) * 100).toFixed(1)
// Breakout room actions
const addBreakoutRoom = () => {
const newId = String(breakoutRooms.length + 1)
setBreakoutRooms([...breakoutRooms, { id: newId, name: `Raum ${newId}`, participants: [] }])
}
const removeBreakoutRoom = (id: string) => {
setBreakoutRooms(breakoutRooms.filter((r) => r.id !== id))
}
return {
// Tab
activeTab, setActiveTab,
// Data
stats, scheduledMeetings, activeMeetings, recordings, loading,
// Modals
showNewMeetingModal, setShowNewMeetingModal,
showJoinModal, setShowJoinModal,
showTranscriptModal, setShowTranscriptModal,
currentMeetingUrl, currentMeetingTitle,
currentRecording, transcriptText, transcriptLoading,
// Recordings
recordingsFilter, setRecordingsFilter,
filteredRecordings, totalStorageBytes, maxStorageGB, storagePercent,
// Breakout
breakoutRooms, breakoutAssignment, setBreakoutAssignment,
breakoutTimer, setBreakoutTimer, hasActiveMeeting,
addBreakoutRoom, removeBreakoutRoom,
// Form
meetingType, setMeetingType,
meetingTitle, setMeetingTitle,
meetingDuration, setMeetingDuration,
meetingDateTime, setMeetingDateTime,
enableLobby, setEnableLobby,
enableRecording, setEnableRecording,
muteOnStart, setMuteOnStart,
creating, errorMessage, setErrorMessage,
// Actions
fetchData, createMeeting, startQuickMeeting,
joinMeeting, openInNewTab, copyMeetingLink,
playRecording, viewTranscript, startTranscription,
downloadRecording, deleteRecording,
}
}