// @ts-nocheck 'use client'; import React, { useEffect, useState, useCallback, useRef } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { MessageSquare, Plus, Send, Clock, CheckCircle2, XCircle, Phone, Paperclip, Mic, MicOff, StickyNote, FileText, User, Shield, Eye, X, MessageCircle, ChevronRight, AlertTriangle, Volume2, Notebook, Upload, Play, Pause, Trash2, Save, File, Image, FileIcon, } from 'lucide-react'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Badge } from '@/components/ui/badge'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog'; import { Separator } from '@/components/ui/separator'; import { ScrollArea } from '@/components/ui/scroll-area'; import { useAppStore } from '@/lib/store'; import { t } from '@/lib/i18n'; import { apiGet, apiPost, apiPut, apiDelete } from '@/lib/api'; import { toast } from 'sonner'; interface CommunicationRoom { id: string; schoolId: string; studentId: string; teacherId: string; roomType: string; status: string; requestedBy: string; acceptedAt: string | null; closedAt: string | null; closeReason: string | null; createdAt: string; updatedAt: string; student?: { id: string; firstName: string; lastName: string }; teacher?: { id: string; firstName: string; lastName: string }; messages?: Array<{ id: string; content: string; messageType: string; createdAt: string; senderId: string; }>; } interface Message { id: string; roomId: string; senderId: string; content: string; messageType: string; fileUrl: string | null; metadata: string | null; isRead: boolean; createdAt: string; sender?: { id: string; firstName: string; lastName: string; role: string }; } interface NotebookItem { id: string; title: string; color: string; notebookType: string; pages?: Array<{ id: string; title: string | null; pageNumber: number }>; } interface TeacherNoteItem { id: string; content: string; createdAt: string; } export default function CommunicationView() { const currentUser = useAppStore((s) => s.currentUser); const [rooms, setRooms] = useState([]); const [selectedRoomId, setSelectedRoomId] = useState(null); const [isLoading, setIsLoading] = useState(true); const [showRequestDialog, setShowRequestDialog] = useState(false); const fetchRooms = useCallback(async () => { try { const data = await apiGet('/api/communication-rooms'); setRooms(data); } catch (error) { console.error('Failed to fetch rooms:', error); } finally { setIsLoading(false); } }, []); useEffect(() => { fetchRooms(); const interval = setInterval(fetchRooms, 5000); return () => { clearInterval(interval); }; }, [fetchRooms]); const role = currentUser?.role; if (isLoading) { return (
); } const requestedRooms = rooms.filter((r) => r.status === 'requested'); const activeRooms = rooms.filter((r) => r.status === 'active'); const closedRooms = rooms.filter((r) => r.status === 'closed'); return (
{/* Header */}

{t('communication.title')}

{role === 'STUDENT' && t('communication.active_conversations')} {role === 'TEACHER' && t('communication.incoming_requests')} {(role === 'SCHOOL_ADMIN' || role === 'VICE_PRINCIPAL') && t('communication.all_rooms')}

{role === 'STUDENT' && ( )}
{/* Main Layout: Room List + Chat */}
{/* Room List */}
{/* Pending Requests (Teacher) */} {role === 'TEACHER' && requestedRooms.length > 0 && ( {t('communication.pending_requests')} ({requestedRooms.length})
{requestedRooms.map((room) => ( setSelectedRoomId(room.id)} currentUserId={currentUser?.id || ''} role={role} /> ))}
)} {/* Active Rooms */} {t('communication.active_conversations')} ({activeRooms.length}) {activeRooms.length === 0 ? (

{t('communication.no_conversations')}

) : (
{activeRooms.map((room) => ( setSelectedRoomId(room.id)} currentUserId={currentUser?.id || ''} role={role} /> ))}
)}
{/* Closed Rooms */} {closedRooms.length > 0 && ( {t('communication.closed')} ({closedRooms.length})
{closedRooms.map((room) => ( setSelectedRoomId(room.id)} currentUserId={currentUser?.id || ''} role={role} /> ))}
)}
{/* Chat Area */}
{selectedRoomId ? ( ) : (

{t('communication.select_conversation')}

)}
{/* Mobile: Show chat as full-screen overlay */} {selectedRoomId && (
setSelectedRoomId(null)} />
)}
{/* Request Dialog */}
); } function RoomListItem({ room, isSelected, onClick, currentUserId, role, }: { room: CommunicationRoom; isSelected: boolean; onClick: () => void; currentUserId: string; role: string | undefined; }) { const isStudent = room.studentId === currentUserId; const otherPerson = isStudent ? room.teacher : room.student; const lastMessage = room.messages?.[0]; const getStatusColor = () => { switch (room.status) { case 'requested': return 'bg-amber-100 text-amber-700 dark:bg-amber-950/30 dark:text-amber-300'; case 'active': return 'bg-emerald-100 text-emerald-700 dark:bg-emerald-950/30 dark:text-emerald-300'; case 'closed': return 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300'; default: return ''; } }; const getStatusLabel = () => { switch (room.status) { case 'requested': return t('communication.requested'); case 'active': return t('communication.active'); case 'closed': return t('communication.closed'); default: return room.status; } }; return (

{otherPerson ? `${otherPerson.firstName} ${otherPerson.lastName}` : 'Unknown'}

{lastMessage && (

{lastMessage.content}

)}
{room.messages && room.messages.length > 0 && room.status === 'active' && ( {room.messages.length} )} {getStatusLabel()}
); } function ChatArea({ roomId, currentUserId, role, onRefresh, isMobile, onClose, }: { roomId: string; currentUserId: string; role: string | undefined; onRefresh: () => void; isMobile?: boolean; onClose?: () => void; }) { const [messages, setMessages] = useState([]); const [newMessage, setNewMessage] = useState(''); const [isLoadingMessages, setIsLoadingMessages] = useState(true); const [isSending, setIsSending] = useState(false); const [room, setRoom] = useState(null); const [isRecording, setIsRecording] = useState(false); const [recordingDuration, setRecordingDuration] = useState(0); const [showTeacherNotes, setShowTeacherNotes] = useState(false); const [teacherNotes, setTeacherNotes] = useState([]); const [newTeacherNote, setNewTeacherNote] = useState(''); const [showShareNotesDialog, setShowShareNotesDialog] = useState(false); const [notebooks, setNotebooks] = useState([]); const [isSharingNote, setIsSharingNote] = useState(false); const [playingVoiceId, setPlayingVoiceId] = useState(null); const [dragOver, setDragOver] = useState(false); const [isUploading, setIsUploading] = useState(false); const messagesEndRef = useRef(null); const mediaRecorderRef = useRef(null); const audioChunksRef = useRef([]); const recordingTimerRef = useRef | null>(null); const audioRef = useRef(null); const fileInputRef = useRef(null); const fetchMessages = useCallback(async () => { try { const data = await apiGet<{ messages: Message[]; total: number }>(`/api/communication-rooms/${roomId}/messages`); setMessages(data.messages); } catch (error) { console.error('Failed to fetch messages:', error); } finally { setIsLoadingMessages(false); } }, [roomId]); const fetchRoom = useCallback(async () => { try { const data = await apiGet(`/api/communication-rooms/${roomId}`); setRoom(data); } catch (error) { console.error('Failed to fetch room:', error); } }, [roomId]); const fetchTeacherNotes = useCallback(async () => { if (role !== 'TEACHER') return; try { const data = await apiGet<{ sharedNotes: Array<{ id: string; content: string; createdAt: string; metadata: string | null }> }>(`/api/communication-rooms/${roomId}/share-notes`); // Convert shared notes to teacher note items for display const notes = data.sharedNotes.map((n) => { let meta: Record = {}; try { meta = n.metadata ? JSON.parse(n.metadata) : {}; } catch { /* ignore */ } return { id: n.id, content: `[${t('communication.note_shared')}: ${meta.notebookTitle || n.content}]`, createdAt: n.createdAt, }; }); setTeacherNotes(notes); } catch { // ignore } }, [roomId, role]); const fetchNotebooks = useCallback(async () => { try { const data = await apiGet }>>('/api/notebooks'); setNotebooks(data); } catch { // ignore } }, []); useEffect(() => { fetchMessages(); fetchRoom(); fetchTeacherNotes(); const interval = setInterval(fetchMessages, 5000); return () => clearInterval(interval); }, [fetchMessages, fetchRoom, fetchTeacherNotes]); useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]); const sendMessage = async (content: string, messageType: string = 'text', metadata?: string) => { if (!content && messageType === 'text') return; setIsSending(true); try { await apiPost(`/api/communication-rooms/${roomId}/messages`, { content, messageType, metadata: metadata || null, }); setNewMessage(''); fetchMessages(); onRefresh(); } catch (error) { toast.error(t('communication.error_send')); } finally { setIsSending(false); } }; const handleAccept = async () => { try { await apiPut(`/api/communication-rooms/${roomId}`, { status: 'active' }); toast.success(t('communication.conversation_accepted')); fetchRoom(); onRefresh(); } catch (error) { toast.error('Error accepting conversation'); } }; const handleReject = async () => { try { await apiPut(`/api/communication-rooms/${roomId}`, { status: 'rejected' }); toast.success(t('communication.conversation_rejected')); onRefresh(); } catch (error) { toast.error('Error rejecting conversation'); } }; const handleClose = async () => { try { await apiPut(`/api/communication-rooms/${roomId}`, { status: 'closed', closeReason: 'Closed by teacher' }); toast.success(t('communication.conversation_closed')); fetchRoom(); onRefresh(); } catch (error) { toast.error('Error closing conversation'); } }; // Voice recording with Web Audio API const startRecording = async () => { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); const mediaRecorder = new MediaRecorder(stream); audioChunksRef.current = []; mediaRecorder.ondataavailable = (event) => { audioChunksRef.current.push(event.data); }; mediaRecorder.onstop = async () => { const audioBlob = new Blob(audioChunksRef.current, { type: 'audio/webm' }); const reader = new FileReader(); reader.onloadend = async () => { const base64Audio = reader.result as string; const metadata = JSON.stringify({ audioData: base64Audio, duration: recordingDuration, mimeType: 'audio/webm', }); await sendMessage( t('communication.voice_message'), 'voice', metadata, ); }; reader.readAsDataURL(audioBlob); stream.getTracks().forEach((track) => track.stop()); }; mediaRecorderRef.current = mediaRecorder; mediaRecorder.start(); setIsRecording(true); setRecordingDuration(0); recordingTimerRef.current = setInterval(() => { setRecordingDuration((prev) => prev + 1); }, 1000); } catch { toast.error(t('communication.voice_recording_error')); } }; const stopRecording = () => { if (mediaRecorderRef.current && isRecording) { mediaRecorderRef.current.stop(); setIsRecording(false); if (recordingTimerRef.current) { clearInterval(recordingTimerRef.current); recordingTimerRef.current = null; } } }; // Voice playback const playVoiceMessage = (msg: Message) => { if (playingVoiceId === msg.id) { if (audioRef.current) { audioRef.current.pause(); audioRef.current = null; } setPlayingVoiceId(null); return; } try { let meta: Record = {}; try { meta = msg.metadata ? JSON.parse(msg.metadata) : {}; } catch { /* ignore */ } const audioData = meta.audioData as string; if (audioData) { if (audioRef.current) { audioRef.current.pause(); } const audio = new Audio(audioData); audio.onended = () => setPlayingVoiceId(null); audio.play(); audioRef.current = audio; setPlayingVoiceId(msg.id); } } catch { // If audio playback fails, just show the message } }; // File sharing const handleFileUpload = async (file: File) => { if (file.size > 10 * 1024 * 1024) { toast.error(t('communication.file_size_limit')); return; } setIsUploading(true); try { const reader = new FileReader(); reader.onloadend = async () => { const base64Data = reader.result as string; const metadata = JSON.stringify({ fileName: file.name, fileSize: file.size, fileType: file.type, fileData: base64Data, }); await sendMessage(file.name, 'file', metadata); }; reader.readAsDataURL(file); } catch { toast.error('Error uploading file'); } finally { setIsUploading(false); } }; const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); setDragOver(true); }; const handleDragLeave = () => { setDragOver(false); }; const handleDrop = (e: React.DragEvent) => { e.preventDefault(); setDragOver(false); const files = e.dataTransfer.files; if (files.length > 0) { handleFileUpload(files[0]); } }; // Note sharing const handleShareNote = async (notebookId: string) => { setIsSharingNote(true); try { await apiPost(`/api/communication-rooms/${roomId}/share-notes`, { notebookId }); toast.success(t('communication.note_shared')); setShowShareNotesDialog(false); fetchMessages(); onRefresh(); } catch { toast.error('Error sharing notebook'); } finally { setIsSharingNote(false); } }; // Save teacher note const handleSaveTeacherNote = async () => { if (!newTeacherNote.trim()) return; try { await apiPost('/api/teacher-notes', { studentId: room?.studentId, category: 'GENERAL', content: newTeacherNote.trim(), isPrivate: true, }); toast.success(t('communication.notes_saved')); setTeacherNotes((prev) => [ { id: Date.now().toString(), content: newTeacherNote.trim(), createdAt: new Date().toISOString() }, ...prev, ]); setNewTeacherNote(''); } catch { toast.error('Error saving note'); } }; const isStudent = room?.studentId === currentUserId; const otherPerson = isStudent ? room?.teacher : room?.student; const isAdmin = role === 'SCHOOL_ADMIN' || role === 'VICE_PRINCIPAL' || role === 'SUPER_ADMIN'; const formatTime = (dateStr: string) => { const date = new Date(dateStr); const now = new Date(); const isToday = date.toDateString() === now.toDateString(); const yesterday = new Date(now); yesterday.setDate(yesterday.getDate() - 1); const isYesterday = date.toDateString() === yesterday.toDateString(); const time = date.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' }); if (isToday) return `${t('communication.today')} ${time}`; if (isYesterday) return `${t('communication.yesterday')} ${time}`; return `${date.toLocaleDateString()} ${time}`; }; const formatDuration = (seconds: number) => { const mins = Math.floor(seconds / 60); const secs = seconds % 60; return `${mins}:${secs.toString().padStart(2, '0')}`; }; const getFileIcon = (fileType: string) => { if (fileType.startsWith('image/')) return ; if (fileType === 'application/pdf') return ; return ; }; return ( {/* Chat Header */}
{isMobile && onClose && ( )}

{otherPerson ? `${otherPerson.firstName} ${otherPerson.lastName}` : 'Unknown'}

{room?.status === 'requested' ? t('communication.requested') : room?.status === 'active' ? t('communication.active') : t('communication.closed')} {isAdmin && ( {t('communication.read_only')} )}
{role === 'TEACHER' && room?.status === 'requested' && ( <> )} {role === 'TEACHER' && room?.status === 'active' && ( <> )}
{/* Teacher Notes Panel */} {showTeacherNotes && (