Classroom
This commit is contained in:
parent
7e402d352c
commit
76615b074b
15 changed files with 133 additions and 143 deletions
|
|
@ -13,8 +13,8 @@ public interface IClassroomAppService : IApplicationService
|
||||||
Task<ClassroomDto> GetAsync(Guid id);
|
Task<ClassroomDto> GetAsync(Guid id);
|
||||||
Task<ClassroomDto> UpdateAsync(Guid id, ClassroomDto input);
|
Task<ClassroomDto> UpdateAsync(Guid id, ClassroomDto input);
|
||||||
Task DeleteAsync(Guid id);
|
Task DeleteAsync(Guid id);
|
||||||
Task<ClassroomDto> StartClassAsync(Guid id);
|
// Task<ClassroomDto> StartClassAsync(Guid id);
|
||||||
Task EndClassAsync(Guid id);
|
// Task EndClassAsync(Guid id);
|
||||||
Task<ClassroomDto> JoinClassAsync(Guid id);
|
Task<ClassroomDto> JoinClassAsync(Guid id);
|
||||||
Task LeaveClassAsync(Guid id);
|
Task LeaveClassAsync(Guid id);
|
||||||
Task<List<ClassAttendanceDto>> GetAttendanceAsync(Guid sessionId);
|
Task<List<ClassAttendanceDto>> GetAttendanceAsync(Guid sessionId);
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Kurs.Platform.Entities;
|
using Kurs.Platform.Entities;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Volo.Abp.Application.Dtos;
|
using Volo.Abp.Application.Dtos;
|
||||||
using Volo.Abp.Domain.Repositories;
|
using Volo.Abp.Domain.Repositories;
|
||||||
|
|
||||||
|
|
@ -88,9 +89,15 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService
|
||||||
classSession.Name = input.Name;
|
classSession.Name = input.Name;
|
||||||
classSession.Description = input.Description;
|
classSession.Description = input.Description;
|
||||||
classSession.Subject = input.Subject;
|
classSession.Subject = input.Subject;
|
||||||
|
classSession.TeacherId = input.TeacherId;
|
||||||
|
classSession.TeacherName = input.TeacherName;
|
||||||
classSession.ScheduledStartTime = input.ScheduledStartTime;
|
classSession.ScheduledStartTime = input.ScheduledStartTime;
|
||||||
|
classSession.ActualStartTime = input.ActualStartTime;
|
||||||
classSession.Duration = input.Duration;
|
classSession.Duration = input.Duration;
|
||||||
classSession.MaxParticipants = input.MaxParticipants;
|
classSession.MaxParticipants = input.MaxParticipants;
|
||||||
|
classSession.IsActive = input.IsActive;
|
||||||
|
classSession.IsScheduled = input.IsScheduled;
|
||||||
|
classSession.SettingsJson = JsonSerializer.Serialize(input.SettingsDto);
|
||||||
|
|
||||||
await _classSessionRepository.UpdateAsync(classSession);
|
await _classSessionRepository.UpdateAsync(classSession);
|
||||||
return ObjectMapper.Map<Classroom, ClassroomDto>(classSession);
|
return ObjectMapper.Map<Classroom, ClassroomDto>(classSession);
|
||||||
|
|
@ -113,7 +120,8 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService
|
||||||
await _classSessionRepository.DeleteAsync(id);
|
await _classSessionRepository.DeleteAsync(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ClassroomDto> StartClassAsync(Guid id)
|
[HttpPut]
|
||||||
|
public async Task<ClassroomDto> StartClassroomAsync(Guid id)
|
||||||
{
|
{
|
||||||
var classSession = await _classSessionRepository.GetAsync(id);
|
var classSession = await _classSessionRepository.GetAsync(id);
|
||||||
|
|
||||||
|
|
@ -127,13 +135,19 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService
|
||||||
throw new InvalidOperationException("Class cannot be started at this time");
|
throw new InvalidOperationException("Class cannot be started at this time");
|
||||||
}
|
}
|
||||||
|
|
||||||
classSession.StartClass();
|
if (classSession.IsActive)
|
||||||
|
throw new InvalidOperationException("Class is already active");
|
||||||
|
|
||||||
|
classSession.IsActive = true;
|
||||||
|
classSession.ActualStartTime = DateTime.Now;
|
||||||
|
|
||||||
await _classSessionRepository.UpdateAsync(classSession);
|
await _classSessionRepository.UpdateAsync(classSession);
|
||||||
|
|
||||||
return ObjectMapper.Map<Classroom, ClassroomDto>(classSession);
|
return ObjectMapper.Map<Classroom, ClassroomDto>(classSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task EndClassAsync(Guid id)
|
[HttpPut]
|
||||||
|
public async Task EndClassroomAsync(Guid id)
|
||||||
{
|
{
|
||||||
var classSession = await _classSessionRepository.GetAsync(id);
|
var classSession = await _classSessionRepository.GetAsync(id);
|
||||||
|
|
||||||
|
|
@ -142,7 +156,12 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService
|
||||||
throw new UnauthorizedAccessException("Only the teacher can end this class");
|
throw new UnauthorizedAccessException("Only the teacher can end this class");
|
||||||
}
|
}
|
||||||
|
|
||||||
classSession.EndClass();
|
if (!classSession.IsActive)
|
||||||
|
throw new InvalidOperationException("Class is not active");
|
||||||
|
|
||||||
|
classSession.IsActive = false;
|
||||||
|
classSession.EndTime = DateTime.Now;
|
||||||
|
|
||||||
await _classSessionRepository.UpdateAsync(classSession);
|
await _classSessionRepository.UpdateAsync(classSession);
|
||||||
|
|
||||||
// Update attendance records
|
// Update attendance records
|
||||||
|
|
|
||||||
|
|
@ -64,27 +64,9 @@ public class Classroom : FullAuditedEntity<Guid>
|
||||||
ChatMessages = new HashSet<ClassChat>();
|
ChatMessages = new HashSet<ClassChat>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StartClass()
|
|
||||||
{
|
|
||||||
if (IsActive)
|
|
||||||
throw new InvalidOperationException("Class is already active");
|
|
||||||
|
|
||||||
IsActive = true;
|
|
||||||
ActualStartTime = DateTime.UtcNow;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void EndClass()
|
|
||||||
{
|
|
||||||
if (!IsActive)
|
|
||||||
throw new InvalidOperationException("Class is not active");
|
|
||||||
|
|
||||||
IsActive = false;
|
|
||||||
EndTime = DateTime.UtcNow;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CanJoin()
|
public bool CanJoin()
|
||||||
{
|
{
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.Now;
|
||||||
var tenMinutesBefore = ScheduledStartTime.AddMinutes(-10);
|
var tenMinutesBefore = ScheduledStartTime.AddMinutes(-10);
|
||||||
var twoHoursAfter = ScheduledStartTime.AddHours(2);
|
var twoHoursAfter = ScheduledStartTime.AddHours(2);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ export interface ClassroomDto {
|
||||||
isScheduled: boolean
|
isScheduled: boolean
|
||||||
participantCount: number
|
participantCount: number
|
||||||
canJoin: boolean
|
canJoin: boolean
|
||||||
settings?: ClassroomSettingsDto
|
settingsDto?: ClassroomSettingsDto
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClassroomSettingsDto {
|
export interface ClassroomSettingsDto {
|
||||||
|
|
|
||||||
|
|
@ -35,3 +35,15 @@ export const deleteClassroom = (id: string) =>
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
url: `/api/app/classroom/${id}`,
|
url: `/api/app/classroom/${id}`,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const startClassroom = (id: string) =>
|
||||||
|
apiService.fetchData({
|
||||||
|
method: 'PUT',
|
||||||
|
url: `/api/app/${id}/start-classroom`,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const endClassroom = (id: string) =>
|
||||||
|
apiService.fetchData({
|
||||||
|
method: 'PUT',
|
||||||
|
url: `/api/app/${id}/end-classroom`,
|
||||||
|
})
|
||||||
|
|
|
||||||
7
ui/src/utils/dateUtils.ts
Normal file
7
ui/src/utils/dateUtils.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
// Veritabanındaki tarihi olduğu gibi göstermek için yardımcı fonksiyon
|
||||||
|
export function showDbDateAsIs(dateStr: string) {
|
||||||
|
if (!dateStr) return ''
|
||||||
|
// ISO formatı veya '2025-08-27 14:15:00.0000000' gibi formatlar için
|
||||||
|
// Sadece ilk 19 karakteri (YYYY-MM-DD HH:mm:ss) al
|
||||||
|
return dateStr.replace('T', ' ').substring(0, 19)
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { showDbDateAsIs } from '@/utils/dateUtils'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { motion } from 'framer-motion'
|
import { motion } from 'framer-motion'
|
||||||
import {
|
import {
|
||||||
|
|
@ -20,6 +21,7 @@ import {
|
||||||
createClassroom,
|
createClassroom,
|
||||||
deleteClassroom,
|
deleteClassroom,
|
||||||
getClassrooms,
|
getClassrooms,
|
||||||
|
startClassroom,
|
||||||
updateClassroom,
|
updateClassroom,
|
||||||
} from '@/services/classroom.service'
|
} from '@/services/classroom.service'
|
||||||
|
|
||||||
|
|
@ -41,7 +43,7 @@ const ClassList: React.FC = () => {
|
||||||
isScheduled: true,
|
isScheduled: true,
|
||||||
participantCount: 0,
|
participantCount: 0,
|
||||||
canJoin: false,
|
canJoin: false,
|
||||||
settings: {
|
settingsDto: {
|
||||||
allowHandRaise: true,
|
allowHandRaise: true,
|
||||||
allowStudentChat: true,
|
allowStudentChat: true,
|
||||||
allowPrivateMessages: true,
|
allowPrivateMessages: true,
|
||||||
|
|
@ -67,7 +69,15 @@ const ClassList: React.FC = () => {
|
||||||
maxResultCount,
|
maxResultCount,
|
||||||
})
|
})
|
||||||
|
|
||||||
setClassList(result.data.items || [])
|
const items = (result.data.items || []).map((item) => ({
|
||||||
|
...item,
|
||||||
|
scheduledStartTime: item.scheduledStartTime
|
||||||
|
? showDbDateAsIs(item.scheduledStartTime)
|
||||||
|
: null,
|
||||||
|
actualStartTime: item.actualStartTime ? showDbDateAsIs(item.actualStartTime) : null,
|
||||||
|
})) as ClassroomDto[]
|
||||||
|
|
||||||
|
setClassList(items)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching classrooms:', error)
|
console.error('Error fetching classrooms:', error)
|
||||||
}
|
}
|
||||||
|
|
@ -138,17 +148,12 @@ const ClassList: React.FC = () => {
|
||||||
setClassroom(newClassEntity)
|
setClassroom(newClassEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleStartClass = (classSession: ClassroomDto) => {
|
const handleStartClass = async (classSession: ClassroomDto) => {
|
||||||
const updatedClass = {
|
await startClassroom(classSession.id!)
|
||||||
...classSession,
|
|
||||||
isActive: true,
|
|
||||||
actualStartTime: new Date().toISOString(),
|
|
||||||
}
|
|
||||||
|
|
||||||
getClassroomList()
|
getClassroomList()
|
||||||
|
|
||||||
if (updatedClass.id) {
|
if (classSession.id) {
|
||||||
navigate(ROUTES_ENUM.protected.admin.classroom.classroom.replace(':id', updatedClass.id))
|
navigate(ROUTES_ENUM.protected.admin.classroom.classroom.replace(':id', classSession.id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -186,16 +191,6 @@ const ClassList: React.FC = () => {
|
||||||
return `${minutes}d kaldı`
|
return `${minutes}d kaldı`
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatDateTime = (dateString: string) => {
|
|
||||||
return new Date(dateString).toLocaleString('tr-TR', {
|
|
||||||
day: '2-digit',
|
|
||||||
month: '2-digit',
|
|
||||||
year: 'numeric',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
|
|
@ -377,7 +372,7 @@ const ClassList: React.FC = () => {
|
||||||
<div className="col-span-1 flex items-center gap-2 px-3 py-2 rounded-lg">
|
<div className="col-span-1 flex items-center gap-2 px-3 py-2 rounded-lg">
|
||||||
<FaCalendarAlt size={14} className="text-gray-500" />
|
<FaCalendarAlt size={14} className="text-gray-500" />
|
||||||
<span className="truncate">
|
<span className="truncate">
|
||||||
{formatDateTime(classSession.scheduledStartTime)}
|
{showDbDateAsIs(classSession.scheduledStartTime)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -536,12 +531,12 @@ const ClassList: React.FC = () => {
|
||||||
<label className="flex items-center space-x-3">
|
<label className="flex items-center space-x-3">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={classroom.settings?.allowHandRaise}
|
checked={classroom.settingsDto?.allowHandRaise}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setClassroom({
|
setClassroom({
|
||||||
...classroom,
|
...classroom,
|
||||||
settings: {
|
settingsDto: {
|
||||||
...classroom.settings!,
|
...classroom.settingsDto!,
|
||||||
allowHandRaise: e.target.checked,
|
allowHandRaise: e.target.checked,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -553,12 +548,12 @@ const ClassList: React.FC = () => {
|
||||||
<label className="flex items-center space-x-3">
|
<label className="flex items-center space-x-3">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={classroom.settings?.allowStudentChat}
|
checked={classroom.settingsDto?.allowStudentChat}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setClassroom({
|
setClassroom({
|
||||||
...classroom,
|
...classroom,
|
||||||
settings: {
|
settingsDto: {
|
||||||
...classroom.settings!,
|
...classroom.settingsDto!,
|
||||||
allowStudentChat: e.target.checked,
|
allowStudentChat: e.target.checked,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -570,12 +565,12 @@ const ClassList: React.FC = () => {
|
||||||
<label className="flex items-center space-x-3">
|
<label className="flex items-center space-x-3">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={classroom.settings?.allowPrivateMessages}
|
checked={classroom.settingsDto?.allowPrivateMessages}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setClassroom({
|
setClassroom({
|
||||||
...classroom,
|
...classroom,
|
||||||
settings: {
|
settingsDto: {
|
||||||
...classroom.settings!,
|
...classroom.settingsDto!,
|
||||||
allowPrivateMessages: e.target.checked,
|
allowPrivateMessages: e.target.checked,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -587,12 +582,12 @@ const ClassList: React.FC = () => {
|
||||||
<label className="flex items-center space-x-3">
|
<label className="flex items-center space-x-3">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={classroom.settings?.allowStudentScreenShare}
|
checked={classroom.settingsDto?.allowStudentScreenShare}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setClassroom({
|
setClassroom({
|
||||||
...classroom,
|
...classroom,
|
||||||
settings: {
|
settingsDto: {
|
||||||
...classroom.settings!,
|
...classroom.settingsDto!,
|
||||||
allowStudentScreenShare: e.target.checked,
|
allowStudentScreenShare: e.target.checked,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -611,12 +606,12 @@ const ClassList: React.FC = () => {
|
||||||
Varsayılan mikrofon durumu
|
Varsayılan mikrofon durumu
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={classroom.settings?.defaultMicrophoneState}
|
value={classroom.settingsDto?.defaultMicrophoneState}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setClassroom({
|
setClassroom({
|
||||||
...classroom,
|
...classroom,
|
||||||
settings: {
|
settingsDto: {
|
||||||
...classroom.settings!,
|
...classroom.settingsDto!,
|
||||||
defaultMicrophoneState: e.target.value as 'muted' | 'unmuted',
|
defaultMicrophoneState: e.target.value as 'muted' | 'unmuted',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -633,12 +628,12 @@ const ClassList: React.FC = () => {
|
||||||
Varsayılan kamera durumu
|
Varsayılan kamera durumu
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={classroom.settings?.defaultCameraState}
|
value={classroom.settingsDto?.defaultCameraState}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setClassroom({
|
setClassroom({
|
||||||
...classroom,
|
...classroom,
|
||||||
settings: {
|
settingsDto: {
|
||||||
...classroom.settings!,
|
...classroom.settingsDto!,
|
||||||
defaultCameraState: e.target.value as 'on' | 'off',
|
defaultCameraState: e.target.value as 'on' | 'off',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -655,12 +650,12 @@ const ClassList: React.FC = () => {
|
||||||
Varsayılan layout
|
Varsayılan layout
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={classroom.settings?.defaultLayout}
|
value={classroom.settingsDto?.defaultLayout}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setClassroom({
|
setClassroom({
|
||||||
...classroom,
|
...classroom,
|
||||||
settings: {
|
settingsDto: {
|
||||||
...classroom.settings!,
|
...classroom.settingsDto!,
|
||||||
defaultLayout: e.target.value,
|
defaultLayout: e.target.value,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -677,12 +672,12 @@ const ClassList: React.FC = () => {
|
||||||
<label className="flex items-center space-x-3">
|
<label className="flex items-center space-x-3">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={classroom.settings?.autoMuteNewParticipants}
|
checked={classroom.settingsDto?.autoMuteNewParticipants}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setClassroom({
|
setClassroom({
|
||||||
...classroom,
|
...classroom,
|
||||||
settings: {
|
settingsDto: {
|
||||||
...classroom.settings!,
|
...classroom.settingsDto!,
|
||||||
autoMuteNewParticipants: e.target.checked,
|
autoMuteNewParticipants: e.target.checked,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -699,7 +694,10 @@ const ClassList: React.FC = () => {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (showCreateModal) setShowCreateModal(false)
|
if (showCreateModal) {
|
||||||
|
setShowCreateModal(false)
|
||||||
|
}
|
||||||
|
|
||||||
if (showEditModal) {
|
if (showEditModal) {
|
||||||
setShowEditModal(false)
|
setShowEditModal(false)
|
||||||
setClassroom(newClassEntity)
|
setClassroom(newClassEntity)
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ 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 { getClassroomById } from '@/services/classroom.service'
|
||||||
|
import { showDbDateAsIs } from '@/utils/dateUtils'
|
||||||
|
|
||||||
type SidePanelType =
|
type SidePanelType =
|
||||||
| 'chat'
|
| 'chat'
|
||||||
|
|
@ -129,7 +130,7 @@ const RoomDetail: React.FC = () => {
|
||||||
allowStudentScreenShare: false,
|
allowStudentScreenShare: false,
|
||||||
allowStudentChat: true,
|
allowStudentChat: true,
|
||||||
allowPrivateMessages: true,
|
allowPrivateMessages: true,
|
||||||
autoMuteNewParticipants: true
|
autoMuteNewParticipants: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const signalRServiceRef = useRef<SignalRService>()
|
const signalRServiceRef = useRef<SignalRService>()
|
||||||
|
|
@ -192,6 +193,7 @@ const RoomDetail: React.FC = () => {
|
||||||
//ClassEntity
|
//ClassEntity
|
||||||
const classEntity = await getClassroomById(params?.id ?? '')
|
const classEntity = await getClassroomById(params?.id ?? '')
|
||||||
if (classEntity) {
|
if (classEntity) {
|
||||||
|
classEntity.data.scheduledStartTime = showDbDateAsIs(classEntity.data.scheduledStartTime)
|
||||||
setClassSession(classEntity.data)
|
setClassSession(classEntity.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import Card from '@/components/ui/Card'
|
import Card from '@/components/ui/Card'
|
||||||
import GrowShrinkTag from '@/components/shared/GrowShrinkTag'
|
import GrowShrinkTag from '@/components/shared/GrowShrinkTag'
|
||||||
import dayjs from 'dayjs'
|
import { showDbDateAsIs } from '@/utils/dateUtils'
|
||||||
|
|
||||||
const Widget = ({
|
const Widget = ({
|
||||||
label,
|
label,
|
||||||
|
|
@ -11,25 +11,25 @@ const Widget = ({
|
||||||
}: {
|
}: {
|
||||||
label: string
|
label: string
|
||||||
datavalue: number
|
datavalue: number
|
||||||
datagrowShrink: number,
|
datagrowShrink: number
|
||||||
valuePrefix: string
|
valuePrefix: string
|
||||||
date: Date
|
date: string
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<h6 className="font-semibold mb-4 text-sm">{label}</h6>
|
<h6 className="font-semibold mb-4 text-sm">{label}</h6>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-bold">{datavalue} {valuePrefix}</h3>
|
<h3 className="font-bold">
|
||||||
<p>
|
{datavalue} {valuePrefix}
|
||||||
<span className="font-semibold">
|
</h3>
|
||||||
{dayjs(date).format('DD MMM')}
|
<p>
|
||||||
</span>
|
<span className="font-semibold">{showDbDateAsIs(date)}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<GrowShrinkTag value={datagrowShrink} suffix="%" />
|
<GrowShrinkTag value={datagrowShrink} suffix="%" />
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,13 @@
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { FaPlus, FaEdit, FaTrashAlt, FaCheckCircle, FaCircle, FaHeart, FaSpinner } from 'react-icons/fa';
|
import {
|
||||||
|
FaPlus,
|
||||||
|
FaEdit,
|
||||||
|
FaTrashAlt,
|
||||||
|
FaCheckCircle,
|
||||||
|
FaCircle,
|
||||||
|
FaHeart,
|
||||||
|
FaSpinner,
|
||||||
|
} from 'react-icons/fa'
|
||||||
import { ForumPost, ForumTopic } from '@/proxy/forum/forum'
|
import { ForumPost, ForumTopic } from '@/proxy/forum/forum'
|
||||||
import { HtmlEditor, ImageUpload, Item, MediaResizing, Toolbar } from 'devextreme-react/html-editor'
|
import { HtmlEditor, ImageUpload, Item, MediaResizing, Toolbar } from 'devextreme-react/html-editor'
|
||||||
import { useStoreState } from '@/store/store'
|
import { useStoreState } from '@/store/store'
|
||||||
|
|
@ -8,6 +16,7 @@ import { Formik, Form, Field, FieldProps } from 'formik'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import { FormContainer, FormItem, Button } from '@/components/ui'
|
import { FormContainer, FormItem, Button } from '@/components/ui'
|
||||||
import { ConfirmDialog } from '@/components/shared'
|
import { ConfirmDialog } from '@/components/shared'
|
||||||
|
import { showDbDateAsIs } from '@/utils/dateUtils'
|
||||||
import {
|
import {
|
||||||
fontFamilyOptions,
|
fontFamilyOptions,
|
||||||
fontSizeOptions,
|
fontSizeOptions,
|
||||||
|
|
@ -136,16 +145,7 @@ export function PostManagement({
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatDate = (value: string | Date) => {
|
const formatDate = (value: string | Date) => {
|
||||||
const date = value instanceof Date ? value : new Date(value)
|
return showDbDateAsIs(typeof value === 'string' ? value : value.toISOString())
|
||||||
if (isNaN(date.getTime())) return 'Invalid Date'
|
|
||||||
|
|
||||||
return new Intl.DateTimeFormat('en', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
}).format(date)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const postValidationSchema = Yup.object().shape({
|
const postValidationSchema = Yup.object().shape({
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import { Formik, Form, Field } from 'formik'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import { FormContainer, FormItem, Button } from '@/components/ui'
|
import { FormContainer, FormItem, Button } from '@/components/ui'
|
||||||
import { ConfirmDialog } from '@/components/shared'
|
import { ConfirmDialog } from '@/components/shared'
|
||||||
|
import { showDbDateAsIs } from '@/utils/dateUtils'
|
||||||
|
|
||||||
interface TopicManagementProps {
|
interface TopicManagementProps {
|
||||||
topics: ForumTopic[]
|
topics: ForumTopic[]
|
||||||
|
|
@ -180,16 +181,7 @@ export function TopicManagement({
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatDate = (value: string | Date) => {
|
const formatDate = (value: string | Date) => {
|
||||||
const date = value instanceof Date ? value : new Date(value)
|
return showDbDateAsIs(typeof value === 'string' ? value : value.toISOString())
|
||||||
if (isNaN(date.getTime())) return 'Invalid Date'
|
|
||||||
|
|
||||||
return new Intl.DateTimeFormat('en', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
}).format(date)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const topicInitialValues = {
|
const topicInitialValues = {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { FaHeart, FaCheckCircle, FaReply } from 'react-icons/fa';
|
import { FaHeart, FaCheckCircle, FaReply } from 'react-icons/fa'
|
||||||
import { ForumPost } from '@/proxy/forum/forum'
|
import { ForumPost } from '@/proxy/forum/forum'
|
||||||
import { AVATAR_URL } from '@/constants/app.constant'
|
import { AVATAR_URL } from '@/constants/app.constant'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
import { showDbDateAsIs } from '@/utils/dateUtils'
|
||||||
|
|
||||||
interface PostCardProps {
|
interface PostCardProps {
|
||||||
post: ForumPost
|
post: ForumPost
|
||||||
|
|
@ -21,16 +22,7 @@ export function ForumPostCard({
|
||||||
}: PostCardProps) {
|
}: PostCardProps) {
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
const formatDate = (value: string | Date) => {
|
const formatDate = (value: string | Date) => {
|
||||||
const date = value instanceof Date ? value : new Date(value)
|
return showDbDateAsIs(typeof value === 'string' ? value : value.toISOString())
|
||||||
if (isNaN(date.getTime())) return 'Invalid Date'
|
|
||||||
|
|
||||||
return new Intl.DateTimeFormat('en', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
}).format(date)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import React, { useState, useEffect } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import { FaTimes, FaSearch, FaFolder, FaRegComment, FaFileAlt } from 'react-icons/fa'
|
import { FaTimes, FaSearch, FaFolder, FaRegComment, FaFileAlt } from 'react-icons/fa'
|
||||||
import { ForumCategory, ForumPost, ForumTopic } from '@/proxy/forum/forum'
|
import { ForumCategory, ForumPost, ForumTopic } from '@/proxy/forum/forum'
|
||||||
|
import { showDbDateAsIs } from '@/utils/dateUtils'
|
||||||
import { useForumSearch } from '@/utils/hooks/useForumSearch'
|
import { useForumSearch } from '@/utils/hooks/useForumSearch'
|
||||||
|
|
||||||
interface SearchModalProps {
|
interface SearchModalProps {
|
||||||
|
|
@ -84,16 +85,7 @@ export function SearchModal({
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatDate = (value: string | Date) => {
|
const formatDate = (value: string | Date) => {
|
||||||
const date = value instanceof Date ? value : new Date(value)
|
return showDbDateAsIs(typeof value === 'string' ? value : value.toISOString())
|
||||||
if (isNaN(date.getTime())) return 'Invalid Date'
|
|
||||||
|
|
||||||
return new Intl.DateTimeFormat('en', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
}).format(date)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTopicTitle = (topicId: string) => {
|
const getTopicTitle = (topicId: string) => {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { FaCalendarAlt, FaUser, FaTag, FaSearch } from 'react-icons/fa'
|
import { FaCalendarAlt, FaUser, FaTag, FaSearch } from 'react-icons/fa'
|
||||||
import dayjs from 'dayjs'
|
import { showDbDateAsIs } from '@/utils/dateUtils'
|
||||||
import 'dayjs/locale/tr'
|
|
||||||
import { BlogCategory, BlogPost } from '@/proxy/blog/blog'
|
import { BlogCategory, BlogPost } from '@/proxy/blog/blog'
|
||||||
import { blogService } from '@/services/blog.service'
|
import { blogService } from '@/services/blog.service'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
|
@ -19,8 +18,6 @@ const Blog = () => {
|
||||||
const [currentPage, setCurrentPage] = useState(1)
|
const [currentPage, setCurrentPage] = useState(1)
|
||||||
const [totalPages, setTotalPages] = useState(1)
|
const [totalPages, setTotalPages] = useState(1)
|
||||||
|
|
||||||
dayjs.locale('tr')
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadBlogData()
|
loadBlogData()
|
||||||
}, [currentPage, selectedCategory])
|
}, [currentPage, selectedCategory])
|
||||||
|
|
@ -200,7 +197,7 @@ const Blog = () => {
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<FaCalendarAlt size={16} className="mr-1" />
|
<FaCalendarAlt size={16} className="mr-1" />
|
||||||
{dayjs(post.publishedAt || post.creationTime).format('DD MMM YYYY')}
|
{showDbDateAsIs(post.publishedAt || post.creationTime)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import React, { useState, useEffect } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import { Link, useParams } from 'react-router-dom'
|
import { Link, useParams } from 'react-router-dom'
|
||||||
import dayjs from 'dayjs'
|
import { showDbDateAsIs } from '@/utils/dateUtils'
|
||||||
import 'dayjs/locale/tr'
|
|
||||||
import { BlogPost } from '@/proxy/blog/blog'
|
import { BlogPost } from '@/proxy/blog/blog'
|
||||||
import { blogService } from '@/services/blog.service'
|
import { blogService } from '@/services/blog.service'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
|
|
@ -28,8 +27,6 @@ const BlogDetail: React.FC = () => {
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
dayjs.locale('tr')
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchBlogPost = async () => {
|
const fetchBlogPost = async () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
|
@ -111,7 +108,7 @@ const BlogDetail: React.FC = () => {
|
||||||
<span>{postData.author?.name}</span>
|
<span>{postData.author?.name}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
{blogPost.publishedAt && dayjs(blogPost.publishedAt).format('DD MMM YYYY')}
|
{blogPost.publishedAt && showDbDateAsIs(blogPost.publishedAt)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue