Chat ve Attandace özellikleri veritabanıından yükleniyor
This commit is contained in:
parent
cf3cb50e1a
commit
f3b953da4f
6 changed files with 104 additions and 64 deletions
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -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 ? (
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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}`,
|
||||||
|
})
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue