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<MealDto> Meals { get; set; } = [];
public List<LeaveDto> Leaves { get; set; } = []; public List<LeaveDto> Leaves { get; set; } = [];
public List<OvertimeDto> Overtimes { 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.FileManagement;
using Kurs.Platform.Intranet; using Kurs.Platform.Intranet;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
@ -35,6 +36,8 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
private readonly IRepository<Meal, Guid> _mealRepository; private readonly IRepository<Meal, Guid> _mealRepository;
private readonly IRepository<Leave, Guid> _leaveRepository; private readonly IRepository<Leave, Guid> _leaveRepository;
private readonly IRepository<Overtime, Guid> _overtimeRepository; private readonly IRepository<Overtime, Guid> _overtimeRepository;
private readonly IRepository<Survey, Guid> _surveyRepository;
private readonly IRepository<SocialPost, Guid> _socialPostRepository;
public IntranetAppService( public IntranetAppService(
ICurrentTenant currentTenant, ICurrentTenant currentTenant,
@ -52,7 +55,9 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
IRepository<ShuttleRoute, Guid> shuttleRouteRepository, IRepository<ShuttleRoute, Guid> shuttleRouteRepository,
IRepository<Meal, Guid> mealRepository, IRepository<Meal, Guid> mealRepository,
IRepository<Leave, Guid> leaveRepository, IRepository<Leave, Guid> leaveRepository,
IRepository<Overtime, Guid> overtimeRepository IRepository<Overtime, Guid> overtimeRepository,
IRepository<Survey, Guid> surveyRepository,
IRepository<SocialPost, Guid> socialPostRepository
) )
{ {
_currentTenant = currentTenant; _currentTenant = currentTenant;
@ -71,6 +76,8 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
_mealRepository = mealRepository; _mealRepository = mealRepository;
_leaveRepository = leaveRepository; _leaveRepository = leaveRepository;
_overtimeRepository = overtimeRepository; _overtimeRepository = overtimeRepository;
_surveyRepository = surveyRepository;
_socialPostRepository = socialPostRepository;
} }
public async Task<IntranetDashboardDto> GetIntranetDashboardAsync() public async Task<IntranetDashboardDto> GetIntranetDashboardAsync()
@ -88,10 +95,34 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService
ShuttleRoutes = await GetShuttleRoutesAsync(), ShuttleRoutes = await GetShuttleRoutesAsync(),
Meals = await GetMealsAsync(), Meals = await GetMealsAsync(),
Leaves = await GetLeavesAsync(), 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() private async Task<List<OvertimeDto>> GetOvertimesAsync()
{ {
var today = DateTime.Now; var today = DateTime.Now;

View file

@ -28,5 +28,17 @@ public class IntranetAutoMapperProfile : Profile
CreateMap<ShuttleRoute, ShuttleRouteDto>() CreateMap<ShuttleRoute, ShuttleRouteDto>()
.ForMember(dest => dest.Route, opt => opt.Ignore()); .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? TenantId { get; set; }
public Guid? EmployeeId { get; set; } public Guid? EmployeeId { get; set; }
public Employee? Employee { get; set; } public Employee Employee { get; set; }
public string Content { get; set; } public string Content { get; set; }
@ -19,8 +19,8 @@ public class SocialPost : FullAuditedEntity<Guid>, IMultiTenant
public bool IsOwnPost { get; set; } public bool IsOwnPost { get; set; }
// Relations // Relations
public SocialLocation? Location { get; set; } public SocialLocation Location { get; set; }
public SocialMedia? Media { get; set; } public SocialMedia Media { get; set; }
public ICollection<SocialComment> Comments { get; set; } public ICollection<SocialComment> Comments { get; set; }
public ICollection<SocialLike> Likes { get; set; } public ICollection<SocialLike> Likes { get; set; }
} }

View file

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

File diff suppressed because it is too large Load diff

View file

@ -28,7 +28,8 @@ export interface IntranetDashboardDto {
meals: MealDto[] meals: MealDto[]
leaves: LeaveDto[] leaves: LeaveDto[]
overtimes: OvertimeDto[] overtimes: OvertimeDto[]
// surveys: Survey[] surveys: SurveyDto[]
socialPosts: SocialPostDto[]
// priorityTasks: TaskDto[] // priorityTasks: TaskDto[]
} }
@ -323,3 +324,97 @@ export interface LeaveDto {
creationTime: Date creationTime: Date
lastModificationTime: 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 // Görev
export interface Task { export interface Task {
@ -16,102 +19,3 @@ export interface Task {
attachments?: { name: string; url: string }[] attachments?: { name: string; url: string }[]
comments: number 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, FaMapMarkerAlt,
FaBriefcase, FaBriefcase,
} from 'react-icons/fa' } 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 { mockEmployees } from '../../../mocks/mockEmployees'
import { mockDepartments } from '../../../mocks/mockDepartments' import { mockDepartments } from '../../../mocks/mockDepartments'
import Widget from '../../../components/common/Widget' import Widget from '../../../components/common/Widget'
import { Container } from '@/components/shared' import { Container } from '@/components/shared'
import { EmployeeDto } from '@/proxy/intranet/models'
// Dinamik organizasyon verisi oluşturma fonksiyonu // Dinamik organizasyon verisi oluşturma fonksiyonu
const generateOrganizationData = (): OrgChart[] => { 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 // Social Wall
import SocialWall from './SocialWall' import SocialWall from './SocialWall'
import { Survey, SurveyAnswer } from '@/types/intranet' import { SurveyDto } from '@/types/intranet'
import { Container } from '@/components/shared' import { Container } from '@/components/shared'
import { usePermission } from '@/utils/hooks/usePermission' 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 { intranetService } from '@/services/intranet.service'
import Announcements from './widgets/Announcements' import Announcements from './widgets/Announcements'
import ShuttleRoute from './widgets/ShuttleRoute' import ShuttleRoute from './widgets/ShuttleRoute'
@ -46,7 +46,7 @@ const WIDGET_ORDER_KEY = 'dashboard-widget-order'
const IntranetDashboard: React.FC = () => { const IntranetDashboard: React.FC = () => {
const { checkPermission } = usePermission() const { checkPermission } = usePermission()
const [selectedAnnouncement, setSelectedAnnouncement] = useState<AnnouncementDto | null>(null) 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 [showSurveyModal, setShowSurveyModal] = useState(false)
const [showLeaveModal, setShowLeaveModal] = useState(false) const [showLeaveModal, setShowLeaveModal] = useState(false)
const [showOvertimeModal, setShowOvertimeModal] = useState(false) const [showOvertimeModal, setShowOvertimeModal] = useState(false)
@ -81,12 +81,12 @@ const IntranetDashboard: React.FC = () => {
targetIndex: null, targetIndex: null,
}) })
const handleTakeSurvey = (survey: Survey) => { const handleTakeSurvey = (survey: SurveyDto) => {
setSelectedSurvey(survey) setSelectedSurvey(survey)
setShowSurveyModal(true) setShowSurveyModal(true)
} }
const handleSubmitSurvey = (answers: SurveyAnswer[]) => { const handleSubmitSurvey = (answers: SurveyAnswerDto[]) => {
setShowSurveyModal(false) setShowSurveyModal(false)
setSelectedSurvey(null) setSelectedSurvey(null)
} }
@ -299,11 +299,15 @@ const IntranetDashboard: React.FC = () => {
onNewOvertime={() => setShowOvertimeModal(true)} onNewOvertime={() => setShowOvertimeModal(true)}
/> />
) )
case 'active-surveys': case 'active-surveys':
return <ActiveSurveys onTakeSurvey={handleTakeSurvey} /> return (
<ActiveSurveys
surveys={intranetDashboard?.surveys || []}
onTakeSurvey={handleTakeSurvey}
/>
)
case 'social-wall': case 'social-wall':
return <SocialWall /> return <SocialWall posts={intranetDashboard?.socialPosts || []} />
case 'priority-tasks': case 'priority-tasks':
return <PriorityTasks /> return <PriorityTasks />
default: default:

View file

@ -11,7 +11,7 @@ import {
} from 'react-icons/fa' } from 'react-icons/fa'
import MediaManager from './MediaManager' import MediaManager from './MediaManager'
import LocationPicker from './LocationPicker' import LocationPicker from './LocationPicker'
import { SocialMedia } from '@/types/intranet' import { SocialMediaDto } from '@/proxy/intranet/models'
interface CreatePostProps { interface CreatePostProps {
onCreatePost: (post: { onCreatePost: (post: {
@ -19,7 +19,7 @@ interface CreatePostProps {
location?: string location?: string
media?: { media?: {
type: 'mixed' | 'poll' type: 'mixed' | 'poll'
mediaItems?: SocialMedia[] mediaItems?: SocialMediaDto[]
poll?: { poll?: {
question: string question: string
options: Array<{ text: string }> options: Array<{ text: string }>
@ -31,7 +31,7 @@ interface CreatePostProps {
const CreatePost: React.FC<CreatePostProps> = ({ onCreatePost }) => { const CreatePost: React.FC<CreatePostProps> = ({ onCreatePost }) => {
const [content, setContent] = useState('') const [content, setContent] = useState('')
const [mediaType, setMediaType] = useState<'media' | 'poll' | null>(null) 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 [location, setLocation] = useState<string | null>(null)
const [pollQuestion, setPollQuestion] = useState('') const [pollQuestion, setPollQuestion] = useState('')
const [pollOptions, setPollOptions] = 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 Zoom from 'yet-another-react-lightbox/plugins/zoom'
import Counter from 'yet-another-react-lightbox/plugins/counter' import Counter from 'yet-another-react-lightbox/plugins/counter'
import 'yet-another-react-lightbox/plugins/counter.css' import 'yet-another-react-lightbox/plugins/counter.css'
import { SocialMedia } from '@/types/intranet' import { SocialMediaDto } from '@/proxy/intranet/models'
interface MediaLightboxProps { interface MediaLightboxProps {
isOpen: boolean isOpen: boolean
onClose: () => void onClose: () => void
media: SocialMedia media: SocialMediaDto
startIndex?: number startIndex?: number
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -1,16 +1,15 @@
import React from 'react' import React from 'react'
import { FaClipboardCheck, FaQuestionCircle, FaUsers, FaClock, FaArrowRight } from 'react-icons/fa' import { FaClipboardCheck, FaQuestionCircle, FaUsers, FaClock, FaArrowRight } from 'react-icons/fa'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { mockSurveys } from '../../../mocks/mockIntranet' import { SurveyDto } from '@/proxy/intranet/models'
import { Survey } from '@/types/intranet' // import { mockSurveys } from '../../../mocks/mockIntranet'
interface ActiveSurveysProps { interface ActiveSurveysProps {
onTakeSurvey: (survey: Survey) => void surveys?: SurveyDto[]
onTakeSurvey: (survey: SurveyDto) => void
} }
const ActiveSurveys: React.FC<ActiveSurveysProps> = ({ onTakeSurvey }) => { const ActiveSurveys: React.FC<ActiveSurveysProps> = ({ surveys, onTakeSurvey }) => {
const activeSurveys = mockSurveys.filter((s) => s.status === 'active').slice(0, 3)
return ( 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"> <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 */} {/* Header with gradient */}
@ -23,7 +22,7 @@ const ActiveSurveys: React.FC<ActiveSurveysProps> = ({ onTakeSurvey }) => {
</div> </div>
<div className="p-6 space-y-4"> <div className="p-6 space-y-4">
{activeSurveys.map((survey, index) => { {surveys?.map((survey, index) => {
const daysLeft = dayjs(survey.deadline).diff(dayjs(), 'day') const daysLeft = dayjs(survey.deadline).diff(dayjs(), 'day')
const urgency = daysLeft <= 3 ? 'urgent' : daysLeft <= 7 ? 'warning' : 'normal' 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="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"> <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" /> <FaClipboardCheck className="w-8 h-8 text-gray-400" />