Chat ve Attandace özellikleri veritabanıından yükleniyor

This commit is contained in:
Sedat ÖZTÜRK 2025-08-29 14:41:47 +03:00
parent cf3cb50e1a
commit f3b953da4f
6 changed files with 104 additions and 64 deletions

View file

@ -17,15 +17,18 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService
private readonly IRepository<Classroom, Guid> _classSessionRepository; private readonly IRepository<Classroom, Guid> _classSessionRepository;
private readonly IRepository<ClassroomParticipant, Guid> _participantRepository; private readonly IRepository<ClassroomParticipant, Guid> _participantRepository;
private readonly IRepository<ClassroomAttandance, Guid> _attendanceRepository; private readonly IRepository<ClassroomAttandance, Guid> _attendanceRepository;
private readonly IRepository<ClassroomChat, Guid> _chatRepository;
public ClassroomAppService( public ClassroomAppService(
IRepository<Classroom, Guid> classSessionRepository, IRepository<Classroom, Guid> classSessionRepository,
IRepository<ClassroomParticipant, Guid> participantRepository, IRepository<ClassroomParticipant, Guid> participantRepository,
IRepository<ClassroomAttandance, Guid> attendanceRepository) IRepository<ClassroomAttandance, Guid> attendanceRepository,
IRepository<ClassroomChat, Guid> chatRepository)
{ {
_classSessionRepository = classSessionRepository; _classSessionRepository = classSessionRepository;
_participantRepository = participantRepository; _participantRepository = participantRepository;
_attendanceRepository = attendanceRepository; _attendanceRepository = attendanceRepository;
_chatRepository = chatRepository;
} }
public async Task<ClassroomDto> GetAsync(Guid id) public async Task<ClassroomDto> GetAsync(Guid id)
@ -271,4 +274,37 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService
return ObjectMapper.Map<List<ClassroomAttandance>, List<ClassroomAttendanceDto>>(attendanceRecords); return ObjectMapper.Map<List<ClassroomAttandance>, List<ClassroomAttendanceDto>>(attendanceRecords);
} }
public async Task<List<ClassroomParticipantDto>> GetParticipantAsync(Guid sessionId)
{
var classSession = await _classSessionRepository.GetAsync(sessionId);
if (classSession.TeacherId != CurrentUser.Id)
{
throw new UnauthorizedAccessException("Only the teacher can view participant");
}
var participantRecords = await _participantRepository.GetListAsync(
x => x.SessionId == sessionId
);
return ObjectMapper.Map<List<ClassroomParticipant>, List<ClassroomParticipantDto>>(participantRecords);
}
public async Task<List<ClassroomChatDto>> GetChatAsync(Guid sessionId)
{
var classSession = await _classSessionRepository.GetAsync(sessionId);
if (classSession.TeacherId != CurrentUser.Id)
{
throw new UnauthorizedAccessException("Only the teacher can view chat");
}
var chatRecords = await _chatRepository.GetListAsync(
x => x.SessionId == sessionId
);
return ObjectMapper.Map<List<ClassroomChat>, List<ClassroomChatDto>>(chatRecords);
}
} }

View file

@ -144,6 +144,7 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
</select> </select>
)} )}
</div> </div>
{/* Messages */} {/* Messages */}
<div className="flex-1 overflow-y-auto p-4 space-y-3"> <div className="flex-1 overflow-y-auto p-4 space-y-3">
{messages.length === 0 ? ( {messages.length === 0 ? (

View file

@ -66,12 +66,13 @@ export type messageType = 'public' | 'private' | 'announcement'
export interface ClassroomChatDto { export interface ClassroomChatDto {
id: string id: string
sessionId: string
senderId: string senderId: string
senderName: string senderName: string
message: string message: string
timestamp: string timestamp: string
isTeacher: boolean isTeacher: boolean
recipientId?: string // Özel mesaj için recipientId?: string
recipientName?: string recipientName?: string
messageType: messageType messageType: messageType
} }

View file

@ -1,4 +1,10 @@
import { ClassroomDto, ClassroomFilterInputDto } from '@/proxy/classroom/models' import {
ClassroomAttendanceDto,
ClassroomChatDto,
ClassroomDto,
ClassroomFilterInputDto,
ClassroomParticipantDto,
} from '@/proxy/classroom/models'
import apiService from './api.service' import apiService from './api.service'
import { PagedAndSortedResultRequestDto, PagedResultDto } from '@/proxy' import { PagedAndSortedResultRequestDto, PagedResultDto } from '@/proxy'
@ -46,3 +52,21 @@ export const endClassroom = (id: string) =>
method: 'PUT', method: 'PUT',
url: `/api/app/classroom/${id}/end-class`, url: `/api/app/classroom/${id}/end-class`,
}) })
export const getClassroomAttandances = (id: string) =>
apiService.fetchData<ClassroomAttendanceDto[]>({
method: 'GET',
url: `/api/app/classroom/attendance/${id}`,
})
export const getClassroomParticipants = (id: string) =>
apiService.fetchData<ClassroomParticipantDto[]>({
method: 'GET',
url: `/api/app/classroom/participant/${id}`,
})
export const getClassroomChats = (id: string) =>
apiService.fetchData<ClassroomChatDto[]>({
method: 'GET',
url: `/api/app/classroom/chat/${id}`,
})

View file

@ -7,7 +7,6 @@ import { useNavigate } from 'react-router-dom'
import { ROUTES_ENUM } from '@/routes/route.constant' import { ROUTES_ENUM } from '@/routes/route.constant'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import { Container } from '@/components/shared'
const Dashboard: React.FC = () => { const Dashboard: React.FC = () => {
const navigate = useNavigate() const navigate = useNavigate()
@ -31,7 +30,7 @@ const Dashboard: React.FC = () => {
title={translate('::' + 'App.Classroom')} title={translate('::' + 'App.Classroom')}
defaultTitle="Kurs Platform" defaultTitle="Kurs Platform"
></Helmet> ></Helmet>
<Container> <div className="flex items-center justify-center p-4">
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
@ -80,7 +79,7 @@ const Dashboard: React.FC = () => {
</motion.button> </motion.button>
</div> </div>
</motion.div> </motion.div>
</Container> </div>
</> </>
) )
} }

View file

@ -53,7 +53,11 @@ import { ParticipantGrid } from '@/components/classroom/ParticipantGrid'
import { ScreenSharePanel } from '@/components/classroom/Panels/ScreenSharePanel' import { ScreenSharePanel } from '@/components/classroom/Panels/ScreenSharePanel'
import { KickParticipantModal } from '@/components/classroom/KickParticipantModal' import { KickParticipantModal } from '@/components/classroom/KickParticipantModal'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
import { getClassroomById } from '@/services/classroom.service' import {
getClassroomAttandances,
getClassroomById,
getClassroomChats,
} from '@/services/classroom.service'
import { showDbDateAsIs } from '@/utils/dateUtils' import { showDbDateAsIs } from '@/utils/dateUtils'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { endClassroom } from '@/services/classroom.service' import { endClassroom } from '@/services/classroom.service'
@ -162,6 +166,22 @@ const RoomDetail: React.FC = () => {
}, },
] ]
const fetchClassAttendances = async () => {
if (!params?.id) return
const attResult = await getClassroomAttandances(params.id)
if (attResult && attResult.data) {
setAttendanceRecords(attResult.data)
}
}
const fetchClassChats = async () => {
if (!params?.id) return
const chatResult = await getClassroomChats(params.id)
if (chatResult && chatResult.data) {
setChatMessages(chatResult.data || [])
}
}
const fetchClassDetails = async () => { const fetchClassDetails = async () => {
const classEntity = await getClassroomById(params?.id ?? '') const classEntity = await getClassroomById(params?.id ?? '')
if (classEntity) { if (classEntity) {
@ -172,6 +192,8 @@ const RoomDetail: React.FC = () => {
useEffect(() => { useEffect(() => {
fetchClassDetails() fetchClassDetails()
fetchClassChats()
fetchClassAttendances()
}, []) }, [])
useEffect(() => { useEffect(() => {
@ -558,52 +580,6 @@ const RoomDetail: React.FC = () => {
) )
} }
// // Demo: Simulate student joining
// const simulateStudentJoin = () => {
// const studentNames = ['Ahmet Yılmaz', 'Fatma Demir', 'Mehmet Kaya', 'Ayşe Özkan', 'Ali Çelik']
// const availableNames = studentNames.filter((name) => !participants.some((p) => p.name === name))
// if (availableNames.length === 0) {
// alert('Tüm demo öğrenciler zaten sınıfta!')
// return
// }
// const randomName = availableNames[Math.floor(Math.random() * availableNames.length)]
// const studentId = crypto.randomUUID()
// setParticipants((prev) => {
// return [
// ...prev,
// {
// id: studentId,
// name: randomName,
// isTeacher: false,
// isAudioMuted: classSettings.autoMuteNewParticipants,
// isVideoMuted: classSettings.defaultCameraState === 'off',
// },
// ]
// })
// // Add attendance record
// setAttendanceRecords((prev: any) => {
// // Check if student already has an active attendance record
// const existingRecord = prev.find((r: any) => r.studentId === studentId && !r.leaveTime)
// if (existingRecord) return prev
// return [
// ...prev,
// {
// id: crypto.randomUUID(),
// sessionId: classSession.id,
// studentId,
// studentName: randomName,
// joinTime: new Date().toISOString(),
// totalDurationMinutes: 0,
// },
// ]
// })
// }
const handleSettingsChange = (newSettings: Partial<ClassroomSettingsDto>) => { const handleSettingsChange = (newSettings: Partial<ClassroomSettingsDto>) => {
setClassSettings((prev) => ({ ...prev, ...newSettings })) setClassSettings((prev) => ({ ...prev, ...newSettings }))
} }
@ -921,17 +897,20 @@ const RoomDetail: React.FC = () => {
<FaUsers className="inline mr-1" size={14} /> <FaUsers className="inline mr-1" size={14} />
Katılımcılar Katılımcılar
</button> </button>
<button
onClick={() => setParticipantsActiveTab('attendance')} {user.role === 'teacher' && (
className={`flex-1 px-3 py-2 text-sm font-medium rounded-md transition-all ${ <button
participantsActiveTab === 'attendance' onClick={() => setParticipantsActiveTab('attendance')}
? 'bg-white text-blue-600 shadow-sm' className={`flex-1 px-3 py-2 text-sm font-medium rounded-md transition-all ${
: 'text-gray-600 hover:text-gray-900' participantsActiveTab === 'attendance'
}`} ? 'bg-white text-blue-600 shadow-sm'
> : 'text-gray-600 hover:text-gray-900'
<FaClipboardList className="inline mr-1" size={14} /> }`}
Katılım Raporu >
</button> <FaClipboardList className="inline mr-1" size={14} />
Katılım Raporu
</button>
)}
</div> </div>
</div> </div>