mockIntranet kaldırıldı

Intranet Dashboard düzeltildi.
This commit is contained in:
Sedat Öztürk 2025-10-29 22:24:40 +03:00
parent 264f802265
commit 988a8d0bfb
22 changed files with 422 additions and 3646 deletions

View file

@ -17,5 +17,7 @@ public class IntranetDashboardDto
public List<MealDto> Meals { get; set; } = [];
public List<LeaveDto> Leaves { get; set; } = [];
public List<OvertimeDto> Overtimes { get; set; } = [];
public List<SurveyDto> Surveys { get; set; } = [];
public List<SocialPostDto> SocialPosts { get; set; } = [];
}

View file

@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using Volo.Abp.Application.Dtos;
namespace Kurs.Platform.Intranet;
public class SocialPostDto : FullAuditedEntityDto<Guid>
{
public Guid? EmployeeId { get; set; }
public EmployeeDto? Employee { get; set; }
public string Content { get; set; }
public int LikeCount { get; set; }
public bool IsLiked { get; set; }
public bool IsOwnPost { get; set; }
public SocialLocationDto? Location { get; set; }
public SocialMediaDto? Media { get; set; }
public List<SocialCommentDto> Comments { get; set; }
public List<SocialLikeDto> Likes { get; set; }
}
public class SocialLocationDto : FullAuditedEntityDto<Guid>
{
public Guid SocialPostId { get; set; }
public string Name { get; set; }
public string? Address { get; set; }
public double? Lat { get; set; }
public double? Lng { get; set; }
public string? PlaceId { get; set; }
}
public class SocialMediaDto : FullAuditedEntityDto<Guid>
{
public Guid SocialPostId { get; set; }
public string Type { get; set; } // image | video | poll
public string[] Urls { get; set; }
// Poll Fields
public string? PollQuestion { get; set; }
public int? PollTotalVotes { get; set; }
public DateTime? PollEndsAt { get; set; }
public string? PollUserVoteId { get; set; }
public List<SocialPollOptionDto> PollOptions { get; set; }
}
public class SocialPollOptionDto : FullAuditedEntityDto<Guid>
{
public Guid SocialMediaId { get; set; }
public string Text { get; set; }
public int Votes { get; set; }
}
public class SocialCommentDto : FullAuditedEntityDto<Guid>
{
public Guid SocialPostId { get; set; }
public Guid? EmployeeId { get; set; }
public EmployeeDto? Employee { get; set; }
public string Content { get; set; }
}
public class SocialLikeDto : FullAuditedEntityDto<Guid>
{
public Guid SocialPostId { get; set; }
public Guid? EmployeeId { get; set; }
public EmployeeDto? Employee { get; set; }
}

View file

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using Volo.Abp.Application.Dtos;
namespace Kurs.Platform.Intranet;
public class SurveyDto : FullAuditedEntityDto<Guid>
{
public string Title { get; set; }
public string Description { get; set; }
public DateTime Deadline { get; set; }
public int Responses { get; set; }
public string Status { get; set; } // draft | active | closed
public bool IsAnonymous { get; set; }
public List<SurveyQuestionDto> Questions { get; set; }
}
public class SurveyQuestionDto : FullAuditedEntityDto<Guid>
{
public Guid SurveyId { get; set; }
public string QuestionText { get; set; }
public string Type { get; set; } // rating | multiple-choice | text | textarea | yes-no
public int Order { get; set; }
public bool IsRequired { get; set; }
public List<SurveyQuestionOptionDto> Options { get; set; }
}
public class SurveyQuestionOptionDto : FullAuditedEntityDto<Guid>
{
public Guid QuestionId { get; set; }
public string Text { get; set; }
public int Order { get; set; }
}
public class SurveyResponseDto : FullAuditedEntityDto<Guid>
{
public Guid SurveyId { get; set; }
public Guid? EmployeeId { get; set; }
public DateTime SubmissionTime { get; set; }
public List<SurveyAnswerDto> Answers { get; set; }
}
public class SurveyAnswerDto : FullAuditedEntityDto<Guid>
{
public Guid ResponseId { get; set; }
public Guid QuestionId { get; set; }
public string QuestionType { get; set; }
public string Value { get; set; }
}

View file

@ -9,6 +9,7 @@ using Kurs.Platform.Entities;
using Kurs.Platform.FileManagement;
using Kurs.Platform.Intranet;
using Microsoft.AspNetCore.Authorization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Volo.Abp.Domain.Repositories;
@ -35,6 +36,8 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
private readonly IRepository<Meal, Guid> _mealRepository;
private readonly IRepository<Leave, Guid> _leaveRepository;
private readonly IRepository<Overtime, Guid> _overtimeRepository;
private readonly IRepository<Survey, Guid> _surveyRepository;
private readonly IRepository<SocialPost, Guid> _socialPostRepository;
public IntranetAppService(
ICurrentTenant currentTenant,
@ -52,7 +55,9 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
IRepository<ShuttleRoute, Guid> shuttleRouteRepository,
IRepository<Meal, Guid> mealRepository,
IRepository<Leave, Guid> leaveRepository,
IRepository<Overtime, Guid> overtimeRepository
IRepository<Overtime, Guid> overtimeRepository,
IRepository<Survey, Guid> surveyRepository,
IRepository<SocialPost, Guid> socialPostRepository
)
{
_currentTenant = currentTenant;
@ -71,6 +76,8 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
_mealRepository = mealRepository;
_leaveRepository = leaveRepository;
_overtimeRepository = overtimeRepository;
_surveyRepository = surveyRepository;
_socialPostRepository = socialPostRepository;
}
public async Task<IntranetDashboardDto> GetIntranetDashboardAsync()
@ -88,10 +95,34 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
ShuttleRoutes = await GetShuttleRoutesAsync(),
Meals = await GetMealsAsync(),
Leaves = await GetLeavesAsync(),
Overtimes = await GetOvertimesAsync()
Overtimes = await GetOvertimesAsync(),
Surveys = await GetSurveysAsync(),
SocialPosts = await GetSocialPostsAsync()
};
}
private async Task<List<SocialPostDto>> GetSocialPostsAsync()
{
var socialPosts = await _socialPostRepository
.WithDetailsAsync(e => e.Employee, e => e.Location, e => e.Media, e => e.Comments, e => e.Likes)
.ContinueWith(t => t.Result.ToList());
return ObjectMapper.Map<List<SocialPost>, List<SocialPostDto>>(socialPosts);
}
private async Task<List<SurveyDto>> GetSurveysAsync()
{
var queryable = await _surveyRepository.GetQueryableAsync();
var surveys = queryable
.Where(s => s.Status == "active")
.Include(s => s.Questions)
.ThenInclude(q => q.Options)
.ToList();
return ObjectMapper.Map<List<Survey>, List<SurveyDto>>(surveys);
}
private async Task<List<OvertimeDto>> GetOvertimesAsync()
{
var today = DateTime.Now;

View file

@ -28,5 +28,17 @@ public class IntranetAutoMapperProfile : Profile
CreateMap<ShuttleRoute, ShuttleRouteDto>()
.ForMember(dest => dest.Route, opt => opt.Ignore());
CreateMap<Survey, SurveyDto>();
CreateMap<SurveyQuestion, SurveyQuestionDto>();
CreateMap<SurveyQuestionOption, SurveyQuestionOptionDto>();
CreateMap<SurveyResponse, SurveyResponseDto>();
CreateMap<SurveyAnswer, SurveyAnswerDto>();
CreateMap<SocialPost, SocialPostDto>();
CreateMap<SocialLocation, SocialLocationDto>();
CreateMap<SocialMedia, SocialMediaDto>();
CreateMap<SocialPollOption, SocialPollOptionDto>();
CreateMap<SocialComment, SocialCommentDto>();
CreateMap<SocialLike, SocialLikeDto>();
}
}

View file

@ -10,7 +10,7 @@ public class SocialPost : FullAuditedEntity<Guid>, IMultiTenant
public Guid? TenantId { get; set; }
public Guid? EmployeeId { get; set; }
public Employee? Employee { get; set; }
public Employee Employee { get; set; }
public string Content { get; set; }
@ -19,8 +19,8 @@ public class SocialPost : FullAuditedEntity<Guid>, IMultiTenant
public bool IsOwnPost { get; set; }
// Relations
public SocialLocation? Location { get; set; }
public SocialMedia? Media { get; set; }
public SocialLocation Location { get; set; }
public SocialMedia Media { get; set; }
public ICollection<SocialComment> Comments { get; set; }
public ICollection<SocialLike> Likes { get; set; }
}

View file

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace Kurs.Platform.Migrations
{
[DbContext(typeof(PlatformDbContext))]
[Migration("20251029074530_Initial")]
[Migration("20251029185945_Initial")]
partial class Initial
{
/// <inheritdoc />

File diff suppressed because it is too large Load diff

View file

@ -28,7 +28,8 @@ export interface IntranetDashboardDto {
meals: MealDto[]
leaves: LeaveDto[]
overtimes: OvertimeDto[]
// surveys: Survey[]
surveys: SurveyDto[]
socialPosts: SocialPostDto[]
// priorityTasks: TaskDto[]
}
@ -323,3 +324,97 @@ export interface LeaveDto {
creationTime: Date
lastModificationTime: Date
}
// Anket Cevap
export interface SurveyAnswerDto {
questionId: string
questionType: 'rating' | 'multiple-choice' | 'text' | 'textarea' | 'yes-no'
value: string | number | string[]
}
// Sosyal Duvar - Comment Interface
export interface SocialCommentDto {
id: string
creator: EmployeeDto
content: string
creationTime: Date
}
export interface SocialPollOptionDto {
id: string
text: string
votes: number
}
// Sosyal Duvar - Social Media Interface
export interface SocialMediaDto {
id?: string
type: 'image' | 'video' | 'poll'
// Ortak alanlar
urls?: string[]
// Anket (poll) ile ilgili alanlar doğrudan burada
pollQuestion?: string
pollOptions?: SocialPollOptionDto[]
pollTotalVotes?: number
pollEndsAt?: Date
pollUserVoteId?: string
}
// Sosyal Duvar - Ana Interface
export interface SocialPostDto {
id: string
employee: EmployeeDto
content: string
locationJson?: string
media?: SocialMediaDto
likeCount: number
isLiked: boolean
likeUsers: EmployeeDto[]
comments: SocialCommentDto[]
isOwnPost: boolean
creationTime: Date
}
// Anket Cevabı
export interface SurveyResponseDto {
id: string
surveyId: string
respondentId?: string // Anonymous ise null
submissionTime: Date
answers: SurveyAnswerDto[]
}
// Anket Sorusu Seçeneği
export interface SurveyQuestionOptionDto {
id: string
text: string
order: number
}
// Anket Sorusu
export interface SurveyQuestionDto {
id: string
surveyId: string
questionText: string
type: 'rating' | 'multiple-choice' | 'text' | 'textarea' | 'yes-no'
order: number
isRequired: boolean
options?: SurveyQuestionOptionDto[]
}
// Anket
export interface SurveyDto {
id: string
title: string
description: string
creatorId: EmployeeDto
creationTime: Date
deadline: Date
questions: SurveyQuestionDto[]
responses: number
targetAudience: string[]
status: 'draft' | 'active' | 'closed'
isAnonymous: boolean
}

View file

@ -1,4 +1,7 @@
import { EmployeeDto } from "@/proxy/intranet/models"
import {
EmployeeDto,
SurveyQuestionDto,
} from '@/proxy/intranet/models'
// Görev
export interface Task {
@ -16,102 +19,3 @@ export interface Task {
attachments?: { name: string; url: string }[]
comments: number
}
// Anket
export interface Survey {
id: string
title: string
description: string
creatorId: EmployeeDto
creationTime: Date
deadline: Date
questions: SurveyQuestion[]
responses: number
targetAudience: string[]
status: 'draft' | 'active' | 'closed'
isAnonymous: boolean
}
// Anket Sorusu
export interface SurveyQuestion {
id: string
surveyId: string
questionText: string
type: 'rating' | 'multiple-choice' | 'text' | 'textarea' | 'yes-no'
order: number
isRequired: boolean
options?: SurveyQuestionOption[]
ratingConfig?: {
min: number
max: number
labels?: { [key: number]: string }
}
}
// Anket Sorusu Seçeneği
export interface SurveyQuestionOption {
id: string
text: string
order: number
}
// Anket Cevabı
export interface SurveyResponse {
id: string
surveyId: string
respondentId?: string // Anonymous ise null
submissionTime: Date
answers: SurveyAnswer[]
}
// Anket Cevap
export interface SurveyAnswer {
questionId: string
questionType: 'rating' | 'multiple-choice' | 'text' | 'textarea' | 'yes-no'
value: string | number | string[]
}
// Sosyal Duvar - Ana Interface
export interface SocialPost {
id: string
creator: EmployeeDto
content: string
locationJson?: string
media?: SocialMedia
likeCount: number
isLiked: boolean
likeUsers: EmployeeDto[]
comments: SocialComment[]
isOwnPost: boolean
creationTime: Date
}
// Sosyal Duvar - Social Media Interface
export interface SocialMedia {
id?: string
type: 'image' | 'video' | 'poll'
// Ortak alanlar
urls?: string[]
// Anket (poll) ile ilgili alanlar doğrudan burada
pollQuestion?: string
pollOptions?: SocialPollOption[]
pollTotalVotes?: number
pollEndsAt?: Date
pollUserVoteId?: string
}
export interface SocialPollOption {
id: string
text: string
votes: number
}
// Sosyal Duvar - Comment Interface
export interface SocialComment {
id: string
creator: EmployeeDto
content: string
creationTime: Date
}

File diff suppressed because it is too large Load diff

View file

@ -15,11 +15,12 @@ import {
FaMapMarkerAlt,
FaBriefcase,
} from 'react-icons/fa'
import { EmployeeDto, HrOrganizationChart as OrgChart } from '../../../types/hr'
import { HrOrganizationChart as OrgChart } from '../../../types/hr'
import { mockEmployees } from '../../../mocks/mockEmployees'
import { mockDepartments } from '../../../mocks/mockDepartments'
import Widget from '../../../components/common/Widget'
import { Container } from '@/components/shared'
import { EmployeeDto } from '@/proxy/intranet/models'
// Dinamik organizasyon verisi oluşturma fonksiyonu
const generateOrganizationData = (): OrgChart[] => {

File diff suppressed because it is too large Load diff

View file

@ -29,10 +29,10 @@ import AnnouncementDetailModal from './modals/AnnouncementDetailModal'
// Social Wall
import SocialWall from './SocialWall'
import { Survey, SurveyAnswer } from '@/types/intranet'
import { SurveyDto } from '@/types/intranet'
import { Container } from '@/components/shared'
import { usePermission } from '@/utils/hooks/usePermission'
import { AnnouncementDto, IntranetDashboardDto } from '@/proxy/intranet/models'
import { AnnouncementDto, IntranetDashboardDto, SurveyAnswerDto } from '@/proxy/intranet/models'
import { intranetService } from '@/services/intranet.service'
import Announcements from './widgets/Announcements'
import ShuttleRoute from './widgets/ShuttleRoute'
@ -46,7 +46,7 @@ const WIDGET_ORDER_KEY = 'dashboard-widget-order'
const IntranetDashboard: React.FC = () => {
const { checkPermission } = usePermission()
const [selectedAnnouncement, setSelectedAnnouncement] = useState<AnnouncementDto | null>(null)
const [selectedSurvey, setSelectedSurvey] = useState<Survey | null>(null)
const [selectedSurvey, setSelectedSurvey] = useState<SurveyDto | null>(null)
const [showSurveyModal, setShowSurveyModal] = useState(false)
const [showLeaveModal, setShowLeaveModal] = useState(false)
const [showOvertimeModal, setShowOvertimeModal] = useState(false)
@ -81,12 +81,12 @@ const IntranetDashboard: React.FC = () => {
targetIndex: null,
})
const handleTakeSurvey = (survey: Survey) => {
const handleTakeSurvey = (survey: SurveyDto) => {
setSelectedSurvey(survey)
setShowSurveyModal(true)
}
const handleSubmitSurvey = (answers: SurveyAnswer[]) => {
const handleSubmitSurvey = (answers: SurveyAnswerDto[]) => {
setShowSurveyModal(false)
setSelectedSurvey(null)
}
@ -299,11 +299,15 @@ const IntranetDashboard: React.FC = () => {
onNewOvertime={() => setShowOvertimeModal(true)}
/>
)
case 'active-surveys':
return <ActiveSurveys onTakeSurvey={handleTakeSurvey} />
return (
<ActiveSurveys
surveys={intranetDashboard?.surveys || []}
onTakeSurvey={handleTakeSurvey}
/>
)
case 'social-wall':
return <SocialWall />
return <SocialWall posts={intranetDashboard?.socialPosts || []} />
case 'priority-tasks':
return <PriorityTasks />
default:

View file

@ -11,7 +11,7 @@ import {
} from 'react-icons/fa'
import MediaManager from './MediaManager'
import LocationPicker from './LocationPicker'
import { SocialMedia } from '@/types/intranet'
import { SocialMediaDto } from '@/proxy/intranet/models'
interface CreatePostProps {
onCreatePost: (post: {
@ -19,7 +19,7 @@ interface CreatePostProps {
location?: string
media?: {
type: 'mixed' | 'poll'
mediaItems?: SocialMedia[]
mediaItems?: SocialMediaDto[]
poll?: {
question: string
options: Array<{ text: string }>
@ -31,7 +31,7 @@ interface CreatePostProps {
const CreatePost: React.FC<CreatePostProps> = ({ onCreatePost }) => {
const [content, setContent] = useState('')
const [mediaType, setMediaType] = useState<'media' | 'poll' | null>(null)
const [mediaItems, setMediaItems] = useState<SocialMedia[]>([])
const [mediaItems, setMediaItems] = useState<SocialMediaDto[]>([])
const [location, setLocation] = useState<string | null>(null)
const [pollQuestion, setPollQuestion] = useState('')
const [pollOptions, setPollOptions] = useState(['', ''])

View file

@ -5,12 +5,12 @@ import Video from 'yet-another-react-lightbox/plugins/video'
import Zoom from 'yet-another-react-lightbox/plugins/zoom'
import Counter from 'yet-another-react-lightbox/plugins/counter'
import 'yet-another-react-lightbox/plugins/counter.css'
import { SocialMedia } from '@/types/intranet'
import { SocialMediaDto } from '@/proxy/intranet/models'
interface MediaLightboxProps {
isOpen: boolean
onClose: () => void
media: SocialMedia
media: SocialMediaDto
startIndex?: number
}

View file

@ -2,11 +2,11 @@ import React, { useState } from 'react'
import { motion } from 'framer-motion'
import { FaTimes, FaLink, FaUpload } from 'react-icons/fa'
import classNames from 'classnames'
import { SocialMedia } from '@/types/intranet'
import { SocialMediaDto } from '@/proxy/intranet/models'
interface MediaManagerProps {
media: SocialMedia[]
onChange: (media: SocialMedia[]) => void
media: SocialMediaDto[]
onChange: (media: SocialMediaDto[]) => void
onClose: () => void
}
@ -19,7 +19,7 @@ const MediaManager: React.FC<MediaManagerProps> = ({ media, onChange, onClose })
const files = e.target.files
if (!files) return
const newMedia: SocialMedia[] = Array.from(files).map((file) => ({
const newMedia: SocialMediaDto[] = Array.from(files).map((file) => ({
id: Math.random().toString(36).substr(2, 9),
type: file.type.startsWith('video/') ? 'video' : 'image',
urls: [URL.createObjectURL(file)],
@ -33,7 +33,7 @@ const MediaManager: React.FC<MediaManagerProps> = ({ media, onChange, onClose })
const handleUrlAdd = () => {
if (!urlInput.trim()) return
const newMedia: SocialMedia = {
const newMedia: SocialMediaDto = {
id: Math.random().toString(36).substr(2, 9),
type: mediaType,
urls: [urlInput]

View file

@ -14,13 +14,13 @@ import {
import MediaLightbox from './MediaLightbox'
import LocationMap from './LocationMap'
import UserProfileCard from './UserProfileCard'
import { SocialPost } from '@/types/intranet'
import { SocialPostDto } from '@/proxy/intranet/models'
dayjs.extend(relativeTime)
dayjs.locale('tr')
interface PostItemProps {
post: SocialPost
post: SocialPostDto
onLike: (postId: string) => void
onComment: (postId: string, content: string) => void
onDelete: (postId: string) => void
@ -258,22 +258,22 @@ const PostItem: React.FC<PostItemProps> = ({ post, onLike, onComment, onDelete,
onMouseLeave={() => setShowUserCard(false)}
>
<img
src={post.creator.avatar || 'https://i.pravatar.cc/150?img=1'}
alt={post.creator.fullName}
src={post.employee.avatar || 'https://i.pravatar.cc/150?img=1'}
alt={post.employee.fullName}
className="w-12 h-12 rounded-full object-cover cursor-pointer ring-2 ring-transparent hover:ring-blue-500 transition-all"
/>
<AnimatePresence>
{showUserCard && (
<UserProfileCard
user={{
id: post.creator.id,
name: post.creator.fullName,
avatar: post.creator.avatar || 'https://i.pravatar.cc/150?img=1',
title: post.creator.jobPosition?.name || 'Çalışan',
email: post.creator.email,
phone: post.creator.phone,
department: post.creator.department?.name,
location: post.creator.workLocation
id: post.employee.id,
name: post.employee.fullName,
avatar: post.employee.avatar || 'https://i.pravatar.cc/150?img=1',
title: post.employee.jobPosition?.name || 'Çalışan',
email: post.employee.email,
phone: post.employee.phone,
department: post.employee.department?.name,
location: post.employee.workLocation
}}
position="bottom"
/>
@ -282,10 +282,10 @@ const PostItem: React.FC<PostItemProps> = ({ post, onLike, onComment, onDelete,
</div>
<div>
<h3 className="font-semibold text-gray-900 dark:text-gray-100">
{post.creator.fullName}
{post.employee.fullName}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
{post.creator.jobPosition?.name || 'Çalışan'} {dayjs(post.creationTime).fromNow()}
{post.employee.jobPosition?.name || 'Çalışan'} {dayjs(post.creationTime).fromNow()}
</p>
</div>
</div>

View file

@ -1,15 +1,13 @@
import React, { useState } from 'react'
import { AnimatePresence } from 'framer-motion'
import PostItem from './PostItem'
import { SocialMedia } from '@/types/intranet'
import CreatePost from './CreatePost'
import { SocialPost } from '@/types/intranet'
import { EmployeeDto } from '@/types/hr'
import { mockSocialPosts } from '@/mocks/mockIntranet'
// import { mockSocialPosts } from '@/mocks/mockIntranet'
import { mockEmployees } from '@/mocks/mockEmployees'
import { EmployeeDto, SocialMediaDto, SocialPostDto } from '@/proxy/intranet/models'
const SocialWall: React.FC = () => {
const [posts, setPosts] = useState<SocialPost[]>(mockSocialPosts)
const SocialWall: React.FC<{ posts: SocialPostDto[] }> = ({ posts }) => {
// const [posts, setPosts] = useState<SocialPost[]>(mockSocialPosts)
const [filter, setFilter] = useState<'all' | 'mine'>('all')
// Ali Öztürk'ü "Siz" kullanıcısı olarak kullan
@ -20,7 +18,7 @@ const SocialWall: React.FC = () => {
location?: string
media?: {
type: 'mixed' | 'poll'
mediaItems?: SocialMedia[]
mediaItems?: SocialMediaDto[]
poll?: {
question: string
options: Array<{ text: string }>
@ -32,24 +30,24 @@ const SocialWall: React.FC = () => {
if (postData.media) {
if (postData.media.type === 'mixed' && postData.media.mediaItems) {
// Convert MediaItems to post format
const images = postData.media.mediaItems.filter(m => m.type === 'image')
const videos = postData.media.mediaItems.filter(m => m.type === 'video')
const images = postData.media.mediaItems.filter((m) => m.type === 'image')
const videos = postData.media.mediaItems.filter((m) => m.type === 'video')
if (images.length > 0 && videos.length === 0) {
mediaForPost = {
type: 'image' as const,
urls: images.map(i => i.urls?.[0]).filter(url => url !== undefined) as string[]
urls: images.map((i) => i.urls?.[0]).filter((url) => url !== undefined) as string[],
}
} else if (videos.length > 0 && images.length === 0) {
mediaForPost = {
type: 'video' as const,
urls: videos[0].urls || []
urls: videos[0].urls || [],
}
} else if (images.length > 0 || videos.length > 0) {
// Mixed media - use first image for now
mediaForPost = {
type: 'image' as const,
urls: images.map(i => i.urls?.[0]).filter(url => url !== undefined) as string[]
urls: images.map((i) => i.urls?.[0]).filter((url) => url !== undefined) as string[],
}
}
} else if (postData.media.type === 'poll' && postData.media.poll) {
@ -59,17 +57,17 @@ const SocialWall: React.FC = () => {
pollOptions: postData.media.poll.options.map((opt, index) => ({
id: `opt-${index}`,
text: opt.text,
votes: 0
votes: 0,
})),
pollTotalVotes: 0,
pollEndsAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
pollEndsAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
}
}
}
const newPost: SocialPost = {
const newPost: SocialPostDto = {
id: Date.now().toString(),
creator: currentUserAuthor,
employee: currentUserAuthor,
content: postData.content,
creationTime: new Date(),
media: mediaForPost,
@ -78,79 +76,77 @@ const SocialWall: React.FC = () => {
isLiked: false,
likeUsers: [],
comments: [],
isOwnPost: true
isOwnPost: true,
}
setPosts([newPost, ...posts])
// setPosts([newPost, ...posts])
}
const handleLike = (postId: string) => {
setPosts(
posts.map((post) => {
if (post.id === postId) {
return {
...post,
likeCount: post.isLiked ? post.likeCount - 1 : post.likeCount + 1,
isLiked: !post.isLiked
}
}
return post
})
)
// setPosts(
// posts.map((post) => {
// if (post.id === postId) {
// return {
// ...post,
// likeCount: post.isLiked ? post.likeCount - 1 : post.likeCount + 1,
// isLiked: !post.isLiked
// }
// }
// return post
// })
// )
}
const handleComment = (postId: string, content: string) => {
setPosts(
posts.map((post) => {
if (post.id === postId) {
const commentAuthor = currentUserAuthor
const newComment = {
id: Date.now().toString(),
creator: commentAuthor,
content,
creationTime: new Date()
}
return {
...post,
comments: [...post.comments, newComment]
}
}
return post
})
)
// setPosts(
// posts.map((post) => {
// if (post.id === postId) {
// const commentAuthor = currentUserAuthor
// const newComment = {
// id: Date.now().toString(),
// creator: commentAuthor,
// content,
// creationTime: new Date()
// }
// return {
// ...post,
// comments: [...post.comments, newComment]
// }
// }
// return post
// })
// )
}
const handleDelete = (postId: string) => {
if (window.confirm('Bu gönderiyi silmek istediğinizden emin misiniz?')) {
setPosts(posts.filter((post) => post.id !== postId))
// setPosts(posts.filter((post) => post.id !== postId))
}
}
const handleVote = (postId: string, optionId: string) => {
setPosts(
posts.map((post) => {
if (post.id === postId && post.media?.type === 'poll' && post.media.pollOptions) {
// If user already voted, don't allow voting again
if (post.media.pollUserVoteId) {
return post
}
return {
...post,
media: {
...post.media,
pollOptions: post.media.pollOptions.map((opt) =>
opt.id === optionId ? { ...opt, votes: opt.votes + 1 } : opt
),
pollTotalVotes: (post.media.pollTotalVotes || 0) + 1,
pollUserVoteId: optionId
}
}
}
return post
})
)
// setPosts(
// posts.map((post) => {
// if (post.id === postId && post.media?.type === 'poll' && post.media.pollOptions) {
// // If user already voted, don't allow voting again
// if (post.media.pollUserVoteId) {
// return post
// }
// return {
// ...post,
// media: {
// ...post.media,
// pollOptions: post.media.pollOptions.map((opt) =>
// opt.id === optionId ? { ...opt, votes: opt.votes + 1 } : opt
// ),
// pollTotalVotes: (post.media.pollTotalVotes || 0) + 1,
// pollUserVoteId: optionId
// }
// }
// }
// return post
// })
// )
}
const filteredPosts = filter === 'mine' ? posts.filter((post) => post.isOwnPost) : posts

View file

@ -1,12 +1,12 @@
import React, { useState } from 'react'
import { motion } from 'framer-motion'
import { FaTimes } from 'react-icons/fa'
import { Survey, SurveyQuestion, SurveyAnswer } from '@/types/intranet'
import { SurveyAnswerDto, SurveyDto, SurveyQuestionDto } from '@/proxy/intranet/models'
interface SurveyModalProps {
survey: Survey
survey: SurveyDto
onClose: () => void
onSubmit: (answers: SurveyAnswer[]) => void
onSubmit: (answers: SurveyAnswerDto[]) => void
}
const SurveyModal: React.FC<SurveyModalProps> = ({ survey, onClose, onSubmit }) => {
@ -14,16 +14,16 @@ const SurveyModal: React.FC<SurveyModalProps> = ({ survey, onClose, onSubmit })
const [errors, setErrors] = useState<{ [questionId: string]: string }>({})
const handleAnswerChange = (questionId: string, value: any) => {
setAnswers(prev => ({
setAnswers((prev) => ({
...prev,
[questionId]: value
[questionId]: value,
}))
// Clear error when user provides an answer
if (errors[questionId]) {
setErrors(prev => ({
setErrors((prev) => ({
...prev,
[questionId]: ''
[questionId]: '',
}))
}
}
@ -31,7 +31,7 @@ const SurveyModal: React.FC<SurveyModalProps> = ({ survey, onClose, onSubmit })
const validateAnswers = (): boolean => {
const newErrors: { [questionId: string]: string } = {}
survey.questions.forEach(question => {
survey.questions.forEach((question) => {
if (question.isRequired && (!answers[question.id] || answers[question.id] === '')) {
newErrors[question.id] = 'Bu alan zorunludur'
}
@ -48,17 +48,18 @@ const SurveyModal: React.FC<SurveyModalProps> = ({ survey, onClose, onSubmit })
return
}
const surveyAnswers: SurveyAnswer[] = survey.questions.map(question => ({
const surveyAnswers: SurveyAnswerDto[] = survey.questions.map((question) => ({
questionId: question.id,
questionType: question.type,
value: answers[question.id] || (question.type === 'multiple-choice' ? '' :
question.type === 'rating' ? 0 : '')
value:
answers[question.id] ||
(question.type === 'multiple-choice' ? '' : question.type === 'rating' ? 0 : ''),
}))
onSubmit(surveyAnswers)
}
const renderQuestion = (question: SurveyQuestion, index: number) => {
const renderQuestion = (question: SurveyQuestionDto, index: number) => {
const questionNumber = index + 1
const hasError = !!errors[question.id]
@ -69,34 +70,9 @@ const SurveyModal: React.FC<SurveyModalProps> = ({ survey, onClose, onSubmit })
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
{questionNumber}. {question.questionText} {question.isRequired && '*'}
</label>
<div className="flex gap-2 flex-wrap">
{Array.from({ length: question.ratingConfig!.max - question.ratingConfig!.min + 1 }, (_, i) => {
const value = question.ratingConfig!.min + i
const label = question.ratingConfig!.labels?.[value] || value.toString()
return (
<label
key={value}
className={`flex flex-col items-center gap-1 px-3 py-2 border rounded-lg cursor-pointer transition-colors ${
answers[question.id] === value
? 'bg-blue-100 border-blue-500 dark:bg-blue-900/30 dark:border-blue-400'
: 'border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700'
}`}
>
<input
type="radio"
name={`question-${question.id}`}
value={value}
checked={answers[question.id] === value}
onChange={(e) => handleAnswerChange(question.id, parseInt(e.target.value))}
className="sr-only"
/>
<span className="font-medium text-gray-900 dark:text-white">{value}</span>
<span className="text-xs text-gray-500 dark:text-gray-400 text-center">{label}</span>
</label>
)
})}
</div>
{hasError && <p className="text-sm text-red-600 dark:text-red-400">{errors[question.id]}</p>}
{hasError && (
<p className="text-sm text-red-600 dark:text-red-400">{errors[question.id]}</p>
)}
</div>
)
@ -107,7 +83,7 @@ const SurveyModal: React.FC<SurveyModalProps> = ({ survey, onClose, onSubmit })
{questionNumber}. {question.questionText} {question.isRequired && '*'}
</label>
<div className="space-y-2">
{question.options?.map(option => (
{question.options?.map((option) => (
<label
key={option.id}
className={`flex items-center gap-3 p-3 border rounded-lg cursor-pointer transition-colors ${
@ -128,7 +104,9 @@ const SurveyModal: React.FC<SurveyModalProps> = ({ survey, onClose, onSubmit })
</label>
))}
</div>
{hasError && <p className="text-sm text-red-600 dark:text-red-400">{errors[question.id]}</p>}
{hasError && (
<p className="text-sm text-red-600 dark:text-red-400">{errors[question.id]}</p>
)}
</div>
)
@ -139,7 +117,7 @@ const SurveyModal: React.FC<SurveyModalProps> = ({ survey, onClose, onSubmit })
{questionNumber}. {question.questionText} {question.isRequired && '*'}
</label>
<div className="flex gap-4">
{['yes', 'no'].map(value => (
{['yes', 'no'].map((value) => (
<label
key={value}
className={`flex items-center gap-2 px-4 py-2 border rounded-lg cursor-pointer transition-colors ${
@ -162,7 +140,9 @@ const SurveyModal: React.FC<SurveyModalProps> = ({ survey, onClose, onSubmit })
</label>
))}
</div>
{hasError && <p className="text-sm text-red-600 dark:text-red-400">{errors[question.id]}</p>}
{hasError && (
<p className="text-sm text-red-600 dark:text-red-400">{errors[question.id]}</p>
)}
</div>
)
@ -181,7 +161,9 @@ const SurveyModal: React.FC<SurveyModalProps> = ({ survey, onClose, onSubmit })
}`}
placeholder="Cevabınızı yazın..."
/>
{hasError && <p className="text-sm text-red-600 dark:text-red-400">{errors[question.id]}</p>}
{hasError && (
<p className="text-sm text-red-600 dark:text-red-400">{errors[question.id]}</p>
)}
</div>
)
@ -200,7 +182,9 @@ const SurveyModal: React.FC<SurveyModalProps> = ({ survey, onClose, onSubmit })
}`}
placeholder="Yorumlarınızı buraya yazabilirsiniz..."
/>
{hasError && <p className="text-sm text-red-600 dark:text-red-400">{errors[question.id]}</p>}
{hasError && (
<p className="text-sm text-red-600 dark:text-red-400">{errors[question.id]}</p>
)}
</div>
)
@ -231,9 +215,7 @@ const SurveyModal: React.FC<SurveyModalProps> = ({ survey, onClose, onSubmit })
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
{survey.title}
</h2>
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1">
{survey.description}
</p>
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1">{survey.description}</p>
</div>
<button
onClick={onClose}
@ -253,8 +235,7 @@ const SurveyModal: React.FC<SurveyModalProps> = ({ survey, onClose, onSubmit })
{!survey.isAnonymous && (
<div className="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-3">
<p className="text-sm text-blue-700 dark:text-blue-300">
Bu anket isim belirtilerek doldurulmaktadır. Yanıtlarınız
kaydedilecektir.
Bu anket isim belirtilerek doldurulmaktadır. Yanıtlarınız kaydedilecektir.
</p>
</div>
)}

View file

@ -1,16 +1,15 @@
import React from 'react'
import { FaClipboardCheck, FaQuestionCircle, FaUsers, FaClock, FaArrowRight } from 'react-icons/fa'
import dayjs from 'dayjs'
import { mockSurveys } from '../../../mocks/mockIntranet'
import { Survey } from '@/types/intranet'
import { SurveyDto } from '@/proxy/intranet/models'
// import { mockSurveys } from '../../../mocks/mockIntranet'
interface ActiveSurveysProps {
onTakeSurvey: (survey: Survey) => void
surveys?: SurveyDto[]
onTakeSurvey: (survey: SurveyDto) => void
}
const ActiveSurveys: React.FC<ActiveSurveysProps> = ({ onTakeSurvey }) => {
const activeSurveys = mockSurveys.filter((s) => s.status === 'active').slice(0, 3)
const ActiveSurveys: React.FC<ActiveSurveysProps> = ({ surveys, onTakeSurvey }) => {
return (
<div className="bg-gradient-to-br from-white to-gray-50 dark:from-gray-800 dark:to-gray-850 rounded-xl shadow-lg border border-gray-200/50 dark:border-gray-700/50 overflow-hidden">
{/* Header with gradient */}
@ -23,7 +22,7 @@ const ActiveSurveys: React.FC<ActiveSurveysProps> = ({ onTakeSurvey }) => {
</div>
<div className="p-6 space-y-4">
{activeSurveys.map((survey, index) => {
{surveys?.map((survey, index) => {
const daysLeft = dayjs(survey.deadline).diff(dayjs(), 'day')
const urgency = daysLeft <= 3 ? 'urgent' : daysLeft <= 7 ? 'warning' : 'normal'
@ -124,7 +123,7 @@ const ActiveSurveys: React.FC<ActiveSurveysProps> = ({ onTakeSurvey }) => {
)
})}
{activeSurveys.length === 0 && (
{surveys?.length === 0 && (
<div className="text-center py-12">
<div className="inline-flex items-center justify-center w-16 h-16 bg-gray-100 dark:bg-gray-700 rounded-full mb-4">
<FaClipboardCheck className="w-8 h-8 text-gray-400" />