From bdf67ec1daa2c80ac9d7f8e0837eb307452dee98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?= <76204082+iamsedatozturk@users.noreply.github.com> Date: Wed, 29 Oct 2025 13:20:21 +0300 Subject: [PATCH] Intranet AppService --- .../FileManagement/FileItemDto.cs | 4 +- .../Intranet/CurrencyDto.cs | 14 + .../Intranet/DepartmentDto.cs | 22 + .../Intranet/EmployeeDto.cs | 61 ++ .../Intranet/EmploymentTypeDto.cs | 10 + .../Intranet/EventCommentDto.cs | 2 +- .../Intranet/EventDto.cs | 4 +- .../Intranet/EventOrganizerDto.cs | 2 +- .../Intranet/ExpenseDto.cs | 30 + .../Intranet/ExpensesDto.cs | 9 + .../Intranet/IIntranetAppService.cs | 2 +- .../Intranet/IntranetDashboardDto.cs | 10 +- .../Intranet/JobPositionDto.cs | 23 + .../Intranet/ReservationDto.cs | 22 + .../Intranet/TrainingDto.cs | 26 + .../Intranet/VisitorDto.cs | 23 + .../FileManagementAppService.cs | 118 ++-- .../Intranet/IntranetAppService.cs | 199 ++++++- .../Intranet/IntranetAutoMapperProfile.cs | 20 + .../Seeds/ListFormSeeder.cs | 4 +- .../BlobStoring/BlobContainerNames.cs | 1 + .../Entities/Tenant/Intranet/Visitor.cs | 7 +- ....cs => 20251029074530_Initial.Designer.cs} | 8 +- ...1_Initial.cs => 20251029074530_Initial.cs} | 2 + .../PlatformDbContextModelSnapshot.cs | 6 + .../Tenants/Seeds/TenantData.json | 106 ++-- .../Tenants/TenantDataSeeder.cs | 3 +- .../Tenants/TenantSeederDto.cs | 1 + .../components/common/MultiSelectEmployee.tsx | 10 +- ui/src/mocks/mockDepartments.ts | 4 +- ui/src/mocks/mockEmployees.ts | 4 +- ui/src/mocks/mockIntranet.ts | 520 ++++++++++-------- ui/src/mocks/mockJobPositions.ts | 4 +- ui/src/proxy/intranet/models.ts | 203 ++++++- ui/src/types/common.ts | 6 +- ui/src/types/crm.ts | 6 +- ui/src/types/hr.ts | 115 +--- ui/src/types/intranet.ts | 102 +--- ui/src/types/mm.ts | 4 +- ui/src/types/pm.ts | 4 +- ui/src/types/ps.ts | 10 +- .../hr/components/DepartmentFormModal.tsx | 6 +- .../hr/components/DepartmentManagement.tsx | 34 +- .../hr/components/DepartmentViewModal.tsx | 6 +- ui/src/views/hr/components/EmployeeCards.tsx | 8 +- ui/src/views/hr/components/EmployeeForm.tsx | 16 +- ui/src/views/hr/components/EmployeeList.tsx | 8 +- ui/src/views/hr/components/EmployeeView.tsx | 6 +- .../hr/components/JobPositionFormModal.tsx | 6 +- .../hr/components/JobPositionViewModal.tsx | 4 +- ui/src/views/hr/components/JobPositions.tsx | 36 +- .../views/hr/components/OrganizationChart.tsx | 12 +- ui/src/views/intranet/Dashboard.tsx | 39 +- ui/src/views/intranet/SocialWall/index.tsx | 4 +- .../intranet/widgets/ActiveReservations.tsx | 21 +- .../intranet/widgets/ExpenseManagement.tsx | 36 +- .../intranet/widgets/RecentDocuments.tsx | 111 +++- .../views/intranet/widgets/TodayBirthdays.tsx | 57 +- .../intranet/widgets/UpcomingTrainings.tsx | 11 +- ui/src/views/intranet/widgets/Visitors.tsx | 35 +- .../maintenance/components/WorkCenterForm.tsx | 4 +- .../views/project/components/ProjectForm.tsx | 4 +- 62 files changed, 1397 insertions(+), 798 deletions(-) create mode 100644 api/src/Kurs.Platform.Application.Contracts/Intranet/CurrencyDto.cs create mode 100644 api/src/Kurs.Platform.Application.Contracts/Intranet/DepartmentDto.cs create mode 100644 api/src/Kurs.Platform.Application.Contracts/Intranet/EmployeeDto.cs create mode 100644 api/src/Kurs.Platform.Application.Contracts/Intranet/EmploymentTypeDto.cs create mode 100644 api/src/Kurs.Platform.Application.Contracts/Intranet/ExpenseDto.cs create mode 100644 api/src/Kurs.Platform.Application.Contracts/Intranet/ExpensesDto.cs create mode 100644 api/src/Kurs.Platform.Application.Contracts/Intranet/JobPositionDto.cs create mode 100644 api/src/Kurs.Platform.Application.Contracts/Intranet/ReservationDto.cs create mode 100644 api/src/Kurs.Platform.Application.Contracts/Intranet/TrainingDto.cs create mode 100644 api/src/Kurs.Platform.Application.Contracts/Intranet/VisitorDto.cs create mode 100644 api/src/Kurs.Platform.Application/Intranet/IntranetAutoMapperProfile.cs rename api/src/Kurs.Platform.EntityFrameworkCore/Migrations/{20251028200151_Initial.Designer.cs => 20251029074530_Initial.Designer.cs} (99%) rename api/src/Kurs.Platform.EntityFrameworkCore/Migrations/{20251028200151_Initial.cs => 20251029074530_Initial.cs} (99%) diff --git a/api/src/Kurs.Platform.Application.Contracts/FileManagement/FileItemDto.cs b/api/src/Kurs.Platform.Application.Contracts/FileManagement/FileItemDto.cs index e7759fcc..b67b7185 100644 --- a/api/src/Kurs.Platform.Application.Contracts/FileManagement/FileItemDto.cs +++ b/api/src/Kurs.Platform.Application.Contracts/FileManagement/FileItemDto.cs @@ -7,7 +7,7 @@ public class FileItemDto { public string Id { get; set; } = string.Empty; public string Name { get; set; } = string.Empty; - public string Type { get; set; } = string.Empty; // "file" or "folder" + public string Type { get; set; } = string.Empty; public long? Size { get; set; } public string Extension { get; set; } = string.Empty; public string MimeType { get; set; } = string.Empty; @@ -16,7 +16,7 @@ public class FileItemDto public string Path { get; set; } = string.Empty; public string ParentId { get; set; } = string.Empty; public bool IsReadOnly { get; set; } - public int? ChildCount { get; set; } // Klasörler için öğe sayısı + public int? ChildCount { get; set; } } public class GetFilesDto diff --git a/api/src/Kurs.Platform.Application.Contracts/Intranet/CurrencyDto.cs b/api/src/Kurs.Platform.Application.Contracts/Intranet/CurrencyDto.cs new file mode 100644 index 00000000..3b402322 --- /dev/null +++ b/api/src/Kurs.Platform.Application.Contracts/Intranet/CurrencyDto.cs @@ -0,0 +1,14 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace Kurs.Platform.Intranet; + +public class CurrencyDto : FullAuditedEntityDto +{ + public string Code { get; set; } // TRY, USD, EUR + public string Symbol { get; set; } // ₺, $, etc. + public string Name { get; set; } // Turkish lira, US dollar, ... + public decimal Rate { get; set; } // TRY başına değer + public bool IsActive { get; set; } + public DateTime? LastUpdated { get; set; } +} diff --git a/api/src/Kurs.Platform.Application.Contracts/Intranet/DepartmentDto.cs b/api/src/Kurs.Platform.Application.Contracts/Intranet/DepartmentDto.cs new file mode 100644 index 00000000..326cfe1f --- /dev/null +++ b/api/src/Kurs.Platform.Application.Contracts/Intranet/DepartmentDto.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using Volo.Abp.Application.Dtos; + +namespace Kurs.Platform.Intranet; + +public class DepartmentDto : FullAuditedEntityDto +{ + public Guid? TenantId { get; set; } + public string Code { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public Guid? ParentDepartmentId { get; set; } + public string ParentDepartmentName { get; set; } + public Guid? ManagerId { get; set; } + public string ManagerName { get; set; } + public Guid? CostCenterId { get; set; } + public string CostCenterName { get; set; } + public decimal Budget { get; set; } + public bool IsActive { get; set; } + public List SubDepartments { get; set; } = []; +} \ No newline at end of file diff --git a/api/src/Kurs.Platform.Application.Contracts/Intranet/EmployeeDto.cs b/api/src/Kurs.Platform.Application.Contracts/Intranet/EmployeeDto.cs new file mode 100644 index 00000000..6329811b --- /dev/null +++ b/api/src/Kurs.Platform.Application.Contracts/Intranet/EmployeeDto.cs @@ -0,0 +1,61 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace Kurs.Platform.Intranet; + +public class EmployeeDto : FullAuditedEntityDto +{ + public Guid? TenantId { get; set; } + + public string Code { get; set; } + public string FullName { get; set; } + public string Avatar { get; set; } + public string NationalId { get; set; } + public DateTime BirthDate { get; set; } + public string Gender { get; set; } + public string MaritalStatus { get; set; } + + // Embedded Address + public string Country { get; set; } + public string City { get; set; } + public string District { get; set; } + public string Street { get; set; } + public string PostalCode { get; set; } + public string Phone { get; set; } + public string PersonalPhone { get; set; } + public string Email { get; set; } + public string Address1 { get; set; } + public string Address2 { get; set; } + + // Emergency contact + public string EmergencyContactName { get; set; } + public string EmergencyContactRelationship { get; set; } + public string EmergencyContactPhone { get; set; } + + public DateTime HireDate { get; set; } + public DateTime? TerminationDate { get; set; } + + public Guid? EmploymentTypeId { get; set; } + public EmploymentTypeDto EmploymentType { get; set; } + + public Guid? JobPositionId { get; set; } + public JobPositionDto JobPosition { get; set; } + + public Guid? DepartmentId { get; set; } + public DepartmentDto Department { get; set; } + + public string WorkLocation { get; set; } + + public Guid? ManagerId { get; set; } + public EmployeeDto Manager { get; set; } + + public decimal BaseSalary { get; set; } + public Guid? CurrencyId { get; set; } + + public string PayrollGroup { get; set; } // e.g., Monthly, Biweekly, Weekly + public Guid? BankAccountId { get; set; } + public Guid? BadgeId { get; set; } + + public string EmployeeStatus { get; set; } + public bool IsActive { get; set; } +} diff --git a/api/src/Kurs.Platform.Application.Contracts/Intranet/EmploymentTypeDto.cs b/api/src/Kurs.Platform.Application.Contracts/Intranet/EmploymentTypeDto.cs new file mode 100644 index 00000000..b0f5ecb7 --- /dev/null +++ b/api/src/Kurs.Platform.Application.Contracts/Intranet/EmploymentTypeDto.cs @@ -0,0 +1,10 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace Kurs.Platform.Intranet; + +public class EmploymentTypeDto : FullAuditedEntityDto +{ + public Guid? TenantId { get; set; } + public string Name { get; set; } +} \ No newline at end of file diff --git a/api/src/Kurs.Platform.Application.Contracts/Intranet/EventCommentDto.cs b/api/src/Kurs.Platform.Application.Contracts/Intranet/EventCommentDto.cs index 2b09c864..c73b231a 100644 --- a/api/src/Kurs.Platform.Application.Contracts/Intranet/EventCommentDto.cs +++ b/api/src/Kurs.Platform.Application.Contracts/Intranet/EventCommentDto.cs @@ -1,6 +1,6 @@ using System; -namespace Kurs.Platform.Public; +namespace Kurs.Platform.Intranet; public class EventCommentDto { diff --git a/api/src/Kurs.Platform.Application.Contracts/Intranet/EventDto.cs b/api/src/Kurs.Platform.Application.Contracts/Intranet/EventDto.cs index 0e6891c8..f04cd3d6 100644 --- a/api/src/Kurs.Platform.Application.Contracts/Intranet/EventDto.cs +++ b/api/src/Kurs.Platform.Application.Contracts/Intranet/EventDto.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; -namespace Kurs.Platform.Public; +namespace Kurs.Platform.Intranet; -public class EventDto +public class EventDto { public string Id { get; set; } public string CategoryName { get; set; } diff --git a/api/src/Kurs.Platform.Application.Contracts/Intranet/EventOrganizerDto.cs b/api/src/Kurs.Platform.Application.Contracts/Intranet/EventOrganizerDto.cs index 03ac0bff..815b54bd 100644 --- a/api/src/Kurs.Platform.Application.Contracts/Intranet/EventOrganizerDto.cs +++ b/api/src/Kurs.Platform.Application.Contracts/Intranet/EventOrganizerDto.cs @@ -1,6 +1,6 @@ using System; -namespace Kurs.Platform.Public; +namespace Kurs.Platform.Intranet; public class EventOrganizerDto { diff --git a/api/src/Kurs.Platform.Application.Contracts/Intranet/ExpenseDto.cs b/api/src/Kurs.Platform.Application.Contracts/Intranet/ExpenseDto.cs new file mode 100644 index 00000000..de7c4317 --- /dev/null +++ b/api/src/Kurs.Platform.Application.Contracts/Intranet/ExpenseDto.cs @@ -0,0 +1,30 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace Kurs.Platform.Intranet; + +public class ExpenseDto : FullAuditedEntityDto +{ + public Guid? TenantId { get; set; } + + public Guid? EmployeeId { get; set; } + public EmployeeDto Employee { get; set; } + + public string Category { get; set; } + public decimal Amount { get; set; } + + public Guid? CurrencyId { get; set; } + public string CurrencyCode { get; set; } + + public DateTime RequestDate { get; set; } + public string Description { get; set; } + public string Project { get; set; } + public string Status { get; set; } + + public Guid? ApproverId { get; set; } + public EmployeeDto Approver { get; set; } + + public DateTime? ApprovalDate { get; set; } + public string RejectionReason { get; set; } + public string Notes { get; set; } +} diff --git a/api/src/Kurs.Platform.Application.Contracts/Intranet/ExpensesDto.cs b/api/src/Kurs.Platform.Application.Contracts/Intranet/ExpensesDto.cs new file mode 100644 index 00000000..60682add --- /dev/null +++ b/api/src/Kurs.Platform.Application.Contracts/Intranet/ExpensesDto.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; +using Kurs.Platform.Intranet; + +public class ExpensesDto +{ + public decimal TotalRequested { get; set; } + public decimal TotalApproved { get; set; } + public List Last5Expenses { get; set; } = []; +} \ No newline at end of file diff --git a/api/src/Kurs.Platform.Application.Contracts/Intranet/IIntranetAppService.cs b/api/src/Kurs.Platform.Application.Contracts/Intranet/IIntranetAppService.cs index d1a574e2..9ac963de 100644 --- a/api/src/Kurs.Platform.Application.Contracts/Intranet/IIntranetAppService.cs +++ b/api/src/Kurs.Platform.Application.Contracts/Intranet/IIntranetAppService.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; using Volo.Abp.Application.Services; -namespace Kurs.Platform.Public; +namespace Kurs.Platform.Intranet; public interface IIntranetAppService : IApplicationService { diff --git a/api/src/Kurs.Platform.Application.Contracts/Intranet/IntranetDashboardDto.cs b/api/src/Kurs.Platform.Application.Contracts/Intranet/IntranetDashboardDto.cs index 3c677db1..c3995b1b 100644 --- a/api/src/Kurs.Platform.Application.Contracts/Intranet/IntranetDashboardDto.cs +++ b/api/src/Kurs.Platform.Application.Contracts/Intranet/IntranetDashboardDto.cs @@ -1,8 +1,16 @@ using System.Collections.Generic; +using Kurs.Platform.FileManagement; -namespace Kurs.Platform.Public; +namespace Kurs.Platform.Intranet; public class IntranetDashboardDto { public List Events { get; set; } = []; + public List Birthdays { get; set; } = []; + public List Visitors { get; set; } = []; + public List Reservations { get; set; } = []; + public List Trainings { get; set; } = []; + public ExpensesDto Expenses { get; set; } = new ExpensesDto(); + public List Documents { get; set; } = []; } + diff --git a/api/src/Kurs.Platform.Application.Contracts/Intranet/JobPositionDto.cs b/api/src/Kurs.Platform.Application.Contracts/Intranet/JobPositionDto.cs new file mode 100644 index 00000000..671fba5c --- /dev/null +++ b/api/src/Kurs.Platform.Application.Contracts/Intranet/JobPositionDto.cs @@ -0,0 +1,23 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace Kurs.Platform.Intranet; + +public class JobPositionDto : FullAuditedEntityDto +{ + public Guid? TenantId { get; set; } + public string Code { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public Guid? DepartmentId { get; set; } + public string DepartmentName { get; set; } + public string Level { get; set; } + public decimal MinSalary { get; set; } + public decimal MaxSalary { get; set; } + public Guid? CurrencyId { get; set; } + public string CurrencyName { get; set; } + public string RequiredSkills { get; set; } + public string Responsibilities { get; set; } + public string Qualifications { get; set; } + public bool IsActive { get; set; } +} \ No newline at end of file diff --git a/api/src/Kurs.Platform.Application.Contracts/Intranet/ReservationDto.cs b/api/src/Kurs.Platform.Application.Contracts/Intranet/ReservationDto.cs new file mode 100644 index 00000000..68851a82 --- /dev/null +++ b/api/src/Kurs.Platform.Application.Contracts/Intranet/ReservationDto.cs @@ -0,0 +1,22 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace Kurs.Platform.Intranet; + +public class ReservationDto : FullAuditedEntityDto +{ + public Guid? TenantId { get; set; } + + public string Type { get; set; } // room | vehicle | equipment + public string ResourceName { get; set; } + + public Guid? EmployeeId { get; set; } + public string EmployeeName { get; set; } // Optional: ilişkili personel ismi + + public DateTime StartDate { get; set; } + public DateTime EndDate { get; set; } + public string Purpose { get; set; } // Amaç + public int? Participants { get; set; } // Katılımcı Sayısı + public string Notes { get; set; } + public string Status { get; set; } // pending | approved | rejected | completed +} diff --git a/api/src/Kurs.Platform.Application.Contracts/Intranet/TrainingDto.cs b/api/src/Kurs.Platform.Application.Contracts/Intranet/TrainingDto.cs new file mode 100644 index 00000000..e24c32f3 --- /dev/null +++ b/api/src/Kurs.Platform.Application.Contracts/Intranet/TrainingDto.cs @@ -0,0 +1,26 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace Kurs.Platform.Intranet; + +public class TrainingDto : FullAuditedEntityDto +{ + public Guid? TenantId { get; set; } + + public string Title { get; set; } + public string Description { get; set; } + public string Instructor { get; set; } + public string Category { get; set; } // technical | soft-skills | management | compliance | other + public string Type { get; set; } // online | classroom | hybrid + public int Duration { get; set; } // saat veya gün olarak + public DateTime StartDate { get; set; } + public DateTime EndDate { get; set; } + public int MaxParticipants { get; set; } + public int Enrolled { get; set; } + public string Status { get; set; } // upcoming | ongoing | completed + public string Location { get; set; } + public string Thumbnail { get; set; } + + // İlişkili veriler + public int CertificateCount { get; set; } // optional: ilişkili sertifika sayısı +} diff --git a/api/src/Kurs.Platform.Application.Contracts/Intranet/VisitorDto.cs b/api/src/Kurs.Platform.Application.Contracts/Intranet/VisitorDto.cs new file mode 100644 index 00000000..f26f0f19 --- /dev/null +++ b/api/src/Kurs.Platform.Application.Contracts/Intranet/VisitorDto.cs @@ -0,0 +1,23 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace Kurs.Platform.Intranet; + +public class VisitorDto : FullAuditedEntityDto +{ + public Guid? TenantId { get; set; } + + public string FullName { get; set; } + public string CompanyName { get; set; } + public string Email { get; set; } + public string Phone { get; set; } + public string Purpose { get; set; } + public DateTime VisitDate { get; set; } + public DateTime? CheckIn { get; set; } + public DateTime? CheckOut { get; set; } + public Guid? EmployeeId { get; set; } + public EmployeeDto Employee { get; set; } + public string Status { get; set; } + public string BadgeNumber { get; set; } + public string Photo { get; set; } +} diff --git a/api/src/Kurs.Platform.Application/FileManagement/FileManagementAppService.cs b/api/src/Kurs.Platform.Application/FileManagement/FileManagementAppService.cs index 43ee599d..f777db7c 100644 --- a/api/src/Kurs.Platform.Application/FileManagement/FileManagementAppService.cs +++ b/api/src/Kurs.Platform.Application/FileManagement/FileManagementAppService.cs @@ -26,13 +26,14 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe private const string FileMetadataSuffix = ".metadata.json"; private const string FolderMarkerSuffix = ".folder"; private const string IndexFileName = "index.json"; - + // Protected system folders that cannot be deleted, renamed, or moved private static readonly HashSet ProtectedFolders = new(StringComparer.OrdinalIgnoreCase) { BlobContainerNames.Avatar, BlobContainerNames.Import, - BlobContainerNames.Activity + BlobContainerNames.Activity, + BlobContainerNames.Intranet }; public FileManagementAppService( @@ -52,11 +53,6 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe return $"files/{tenantId}/"; } - private string GenerateFileId() - { - return Guid.NewGuid().ToString("N"); - } - private string EncodePathAsId(string path) { // Path'deki '/' karakterlerini '|' ile değiştir URL-safe hale getirmek için @@ -74,13 +70,13 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe // Get the root folder name from path var pathParts = path.Split('/', StringSplitOptions.RemoveEmptyEntries); if (pathParts.Length == 0) return false; - + var rootFolder = pathParts[0]; var isProtected = ProtectedFolders.Contains(rootFolder); - + Logger.LogInformation($"IsProtectedFolder - Path: '{path}', RootFolder: '{rootFolder}', IsProtected: {isProtected}"); Logger.LogInformation($"Protected folders: {string.Join(", ", ProtectedFolders)}"); - + return isProtected; } @@ -88,14 +84,14 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe { var decodedPath = DecodeIdAsPath(id); Logger.LogInformation($"ValidateNotProtectedFolder - ID: {id}, DecodedPath: {decodedPath}, Operation: {operation}"); - + if (IsProtectedFolder(decodedPath)) { var folderName = decodedPath.Split('/')[0]; Logger.LogWarning($"Blocked {operation} operation on protected folder: {folderName}"); throw new UserFriendlyException($"Cannot {operation} system folder '{folderName}'. This folder is protected."); } - + Logger.LogInformation($"Folder {decodedPath} is not protected, allowing {operation}"); } @@ -108,7 +104,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe { var items = new List(); var cdnBasePath = _configuration["App:CdnPath"]; - + if (string.IsNullOrEmpty(cdnBasePath)) { Logger.LogWarning("CDN path is not configured"); @@ -117,7 +113,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe var tenantId = _currentTenant.Id?.ToString() ?? "host"; var fullPath = Path.Combine(cdnBasePath, tenantId); - + if (!string.IsNullOrEmpty(folderPath)) { fullPath = Path.Combine(fullPath, folderPath); @@ -137,7 +133,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe { var dirInfo = new DirectoryInfo(dir); var relativePath = string.IsNullOrEmpty(folderPath) ? dirInfo.Name : $"{folderPath}/{dirInfo.Name}"; - + // Klasör içindeki öğe sayısını hesapla var childCount = 0; try @@ -151,7 +147,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe Logger.LogWarning(ex, "Error counting items in folder: {FolderPath}", dir); childCount = 0; } - + items.Add(new FileMetadata { Id = EncodePathAsId(relativePath), @@ -173,7 +169,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe { var fileInfo = new FileInfo(file); var relativePath = string.IsNullOrEmpty(folderPath) ? fileInfo.Name : $"{folderPath}/{fileInfo.Name}"; - + items.Add(new FileMetadata { Id = EncodePathAsId(relativePath), @@ -198,24 +194,6 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe } } - private async Task> GetCustomFoldersAsync() - { - var indexPath = GetTenantPrefix() + IndexFileName; - - try - { - var indexBytes = await _blobContainer.GetAllBytesOrNullAsync(indexPath); - if (indexBytes == null) return new List(); - - var indexJson = Encoding.UTF8.GetString(indexBytes); - return JsonSerializer.Deserialize>(indexJson) ?? new List(); - } - catch - { - return new List(); - } - } - private async Task SaveFolderIndexAsync(List items, string? parentId = null) { var indexPath = GetTenantPrefix() + (string.IsNullOrEmpty(parentId) ? IndexFileName : $"{parentId}/{IndexFileName}"); @@ -244,13 +222,12 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe { var items = await GetFolderIndexAsync(parentId); - var result = items.Select(metadata => { + var result = items.Select(metadata => + { var isRootLevel = string.IsNullOrEmpty(parentId); var isProtected = IsProtectedFolder(metadata.Path); var finalIsReadOnly = metadata.IsReadOnly || (isRootLevel && isProtected); - - Logger.LogInformation($"Item: {metadata.Name}, Path: {metadata.Path}, IsRootLevel: {isRootLevel}, IsProtected: {isProtected}, FinalIsReadOnly: {finalIsReadOnly}"); - + return new FileItemDto { Id = metadata.Id, @@ -283,7 +260,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe var tenantId = _currentTenant.Id?.ToString() ?? "host"; var parentPath = Path.Combine(cdnBasePath, tenantId); - + string? decodedParentId = null; if (!string.IsNullOrEmpty(input.ParentId)) { @@ -309,7 +286,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe Directory.CreateDirectory(folderPath); var newFolderPath = string.IsNullOrEmpty(decodedParentId) ? input.Name : $"{decodedParentId}/{input.Name}"; - + var metadata = new FileMetadata { Id = EncodePathAsId(newFolderPath), @@ -338,10 +315,6 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe public async Task UploadFileAsync([FromForm] UploadFileDto input) { - // Debug logging - Logger.LogInformation("UploadFileAsync called with: FileName={FileName}, ParentId={ParentId}, FilesCount={FilesCount}", - input.FileName, input.ParentId, input.Files?.Length ?? 0); - ValidateFileName(input.FileName); // Decode parent ID if provided @@ -371,7 +344,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe var tenantId = _currentTenant.Id?.ToString() ?? "host"; var fullCdnPath = Path.Combine(cdnBasePath, tenantId); - + if (!string.IsNullOrEmpty(decodedParentId)) { fullCdnPath = Path.Combine(fullCdnPath, decodedParentId); @@ -416,7 +389,6 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe }; // File system'e kaydedildi, index güncellemeye gerek yok - return new FileItemDto { Id = metadata.Id, @@ -437,7 +409,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe { // Check if this is a protected system folder ValidateNotProtectedFolder(id, "rename"); - + ValidateFileName(input.Name); var metadata = await FindItemMetadataAsync(id); @@ -566,7 +538,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe { // Check if this is a protected system folder ValidateNotProtectedFolder(id, "delete"); - + var cdnBasePath = _configuration["App:CdnPath"]; if (string.IsNullOrEmpty(cdnBasePath)) { @@ -615,7 +587,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe { // Check if this is a protected system folder ValidateNotProtectedFolder(itemId, "delete"); - + var actualPath = DecodeIdAsPath(itemId); var fullPath = Path.Combine(cdnBasePath, tenantId, actualPath); @@ -661,7 +633,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe var tenantId = _currentTenant.Id?.ToString() ?? "host"; var basePath = Path.Combine(cdnBasePath, tenantId); - + string? targetPath = null; if (!string.IsNullOrEmpty(input.TargetFolderId)) { @@ -680,11 +652,11 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe // Get source item name var sourceItemName = Path.GetFileName(sourcePath); - + // Generate unique name if item already exists in target var targetItemPath = string.IsNullOrEmpty(targetPath) ? sourceItemName : $"{targetPath}/{sourceItemName}"; var targetFullPath = Path.Combine(basePath, targetItemPath); - + var uniqueTargetPath = GetUniqueItemPath(targetFullPath, sourceItemName); var finalTargetPath = uniqueTargetPath.Replace(basePath + Path.DirectorySeparatorChar, "").Replace(Path.DirectorySeparatorChar, '/'); @@ -692,7 +664,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe { // Copy directory recursively CopyDirectory(sourceFullPath, uniqueTargetPath); - + var dirInfo = new DirectoryInfo(uniqueTargetPath); copiedItems.Add(new FileItemDto { @@ -714,12 +686,12 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe { Directory.CreateDirectory(targetDir); } - + File.Copy(sourceFullPath, uniqueTargetPath); - + var fileInfo = new FileInfo(uniqueTargetPath); var extension = fileInfo.Extension; - + copiedItems.Add(new FileItemDto { Id = EncodePathAsId(finalTargetPath), @@ -769,7 +741,7 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe var tenantId = _currentTenant.Id?.ToString() ?? "host"; var basePath = Path.Combine(cdnBasePath, tenantId); - + string? targetPath = null; if (!string.IsNullOrEmpty(input.TargetFolderId)) { @@ -791,11 +763,11 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe // Get source item name var sourceItemName = Path.GetFileName(sourcePath); - + // Generate target path var targetItemPath = string.IsNullOrEmpty(targetPath) ? sourceItemName : $"{targetPath}/{sourceItemName}"; var targetFullPath = Path.Combine(basePath, targetItemPath); - + // Check if moving to same location if (Path.GetFullPath(sourceFullPath) == Path.GetFullPath(targetFullPath)) { @@ -815,9 +787,9 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe { Directory.CreateDirectory(targetDir); } - + Directory.Move(sourceFullPath, uniqueTargetPath); - + var dirInfo = new DirectoryInfo(uniqueTargetPath); movedItems.Add(new FileItemDto { @@ -839,12 +811,12 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe { Directory.CreateDirectory(targetDir); } - + File.Move(sourceFullPath, uniqueTargetPath); - + var fileInfo = new FileInfo(uniqueTargetPath); var extension = fileInfo.Extension; - + movedItems.Add(new FileItemDto { Id = EncodePathAsId(finalTargetPath), @@ -949,10 +921,10 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe var nameWithoutExt = Path.GetFileNameWithoutExtension(originalFileName); var extension = Path.GetExtension(originalFileName); - + var counter = 1; string uniqueName; - + do { uniqueName = $"{nameWithoutExt} ({counter}){extension}"; @@ -1070,19 +1042,19 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe // Decode the folderId to get the actual path var decodedPath = DecodeIdAsPath(folderId); Logger.LogInformation($"GetFolderPath - FolderId: {folderId}, DecodedPath: {decodedPath}"); - + // Split path into parts and build breadcrumb var pathParts = decodedPath.Split('/', StringSplitOptions.RemoveEmptyEntries); var currentEncodedPath = ""; - + for (int i = 0; i < pathParts.Length; i++) { // Build the path up to current level var pathUpToCurrent = string.Join("/", pathParts.Take(i + 1)); currentEncodedPath = EncodePathAsId(pathUpToCurrent); - + Logger.LogInformation($"PathItem {i}: Name='{pathParts[i]}', Id='{currentEncodedPath}', PathUpToCurrent='{pathUpToCurrent}'"); - + pathItems.Add(new PathItemDto { Id = currentEncodedPath, @@ -1104,10 +1076,10 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe var directory = Path.GetDirectoryName(targetPath) ?? ""; var nameWithoutExt = Path.GetFileNameWithoutExtension(originalName); var extension = Path.GetExtension(originalName); - + var counter = 1; string newPath; - + do { var newName = $"{nameWithoutExt} ({counter}){extension}"; diff --git a/api/src/Kurs.Platform.Application/Intranet/IntranetAppService.cs b/api/src/Kurs.Platform.Application/Intranet/IntranetAppService.cs index 27e8c302..c2c0abe1 100644 --- a/api/src/Kurs.Platform.Application/Intranet/IntranetAppService.cs +++ b/api/src/Kurs.Platform.Application/Intranet/IntranetAppService.cs @@ -1,47 +1,216 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading.Tasks; +using Kurs.Platform.BlobStoring; using Kurs.Platform.Entities; +using Kurs.Platform.FileManagement; +using Kurs.Platform.Intranet; using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; using Volo.Abp.Domain.Repositories; +using Volo.Abp.MultiTenancy; namespace Kurs.Platform.Public; [Authorize] public class IntranetAppService : PlatformAppService, IIntranetAppService { + private readonly ICurrentTenant _currentTenant; + private readonly BlobManager _blobContainer; + private readonly IConfiguration _configuration; + private readonly IRepository _eventRepository; - private readonly IRepository _eventCategoryRepository; - private readonly IRepository _eventTypeRepository; - private readonly IRepository _eventPhotoRepository; - private readonly IRepository _eventCommentRepository; private readonly IRepository _employeeRepository; + private readonly IRepository _visitorRepository; + private readonly IRepository _reservationRepository; + private readonly IRepository _trainingRepository; + private readonly IRepository _expenseRepository; public IntranetAppService( + ICurrentTenant currentTenant, + BlobManager blobContainer, + IConfiguration configuration, + IRepository eventRepository, - IRepository eventCategoryRepository, - IRepository eventTypeRepository, - IRepository eventPhotoRepository, - IRepository eventCommentRepository, - IRepository employeeRepository) + IRepository employeeRepository, + IRepository visitorRepository, + IRepository reservationRepository, + IRepository trainingRepository, + IRepository expenseRepository + ) { + _currentTenant = currentTenant; + _blobContainer = blobContainer; + _configuration = configuration; + _eventRepository = eventRepository; - _eventCategoryRepository = eventCategoryRepository; - _eventTypeRepository = eventTypeRepository; - _eventPhotoRepository = eventPhotoRepository; - _eventCommentRepository = eventCommentRepository; _employeeRepository = employeeRepository; + _visitorRepository = visitorRepository; + _reservationRepository = reservationRepository; + _trainingRepository = trainingRepository; + _expenseRepository = expenseRepository; } public async Task GetIntranetDashboardAsync() { return new IntranetDashboardDto { - Events = await GetUpcomingEventsAsync() + Events = await GetUpcomingEventsAsync(), + Birthdays = await GetBirthdaysAsync(), + Visitors = await GetVisitorsAsync(), + Reservations = await GetReservationsAsync(), + Trainings = await GetTrainingsAsync(), + Expenses = await GetExpensesAsync(), + Documents = await GetIntranetDocumentsAsync(BlobContainerNames.Intranet) }; } + public async Task> GetIntranetDocumentsAsync(string folderPath) + { + var items = new List(); + var cdnBasePath = _configuration["App:CdnPath"]; + + if (string.IsNullOrEmpty(cdnBasePath)) + { + Logger.LogWarning("CDN path is not configured"); + return items; + } + + var tenantId = _currentTenant.Id?.ToString() ?? "host"; + var fullPath = Path.Combine(cdnBasePath, tenantId); + + if (!string.IsNullOrEmpty(folderPath)) + { + fullPath = Path.Combine(fullPath, folderPath); + } + + if (!Directory.Exists(fullPath)) + { + Logger.LogWarning($"Directory not found: {fullPath}"); + return items; + } + + var files = Directory.GetFiles(fullPath); + foreach (var file in files) + { + var fileInfo = new FileInfo(file); + var relativePath = string.IsNullOrEmpty(folderPath) ? fileInfo.Name : $"{folderPath}/{fileInfo.Name}"; + + items.Add(new FileItemDto + { + Id = Guid.NewGuid().ToString(), + Name = fileInfo.Name, + Type = "file", + Size = fileInfo.Length, + Extension = fileInfo.Extension, + MimeType = GetMimeType(fileInfo.Extension), + CreatedAt = fileInfo.CreationTime, + ModifiedAt = fileInfo.LastWriteTime, + Path = relativePath, + ParentId = string.Empty, + IsReadOnly = false, + ChildCount = 0 + }); + } + + return items.OrderBy(x => x.Name).ToList(); + } + + private string GetMimeType(string extension) + { + return extension.ToLowerInvariant() switch + { + ".pdf" => "application/pdf", + ".doc" => "application/msword", + ".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + ".xls" => "application/vnd.ms-excel", + ".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ".ppt" => "application/vnd.ms-powerpoint", + ".pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation", + ".jpg" or ".jpeg" => "image/jpeg", + ".png" => "image/png", + ".gif" => "image/gif", + ".txt" => "text/plain", + ".zip" => "application/zip", + ".rar" => "application/x-rar-compressed", + _ => "application/octet-stream" + }; + } + + private async Task GetExpensesAsync() + { + var queryable = await _expenseRepository.GetQueryableAsync(); + + var today = DateTime.Today; + var oneMonthAgo = today.AddMonths(-1); + + // Son 1 ay içerisindeki kayıtlar + var lastMonthExpenses = queryable + .Where(a => a.RequestDate >= oneMonthAgo && a.RequestDate <= today); + + // Son 1 aydaki toplam talep miktarı + var totalRequested = lastMonthExpenses.Sum(a => a.Amount); + + // Son 1 aydaki onaylanan harcamaların toplamı + var totalApproved = lastMonthExpenses + .Where(a => a.Status == "approved") + .Sum(a => a.Amount); + + // Son 5 kayıt + var last5Expenses = queryable + .OrderByDescending(a => a.RequestDate) + .Take(5) + .ToList(); + + // Map işlemleri + var last5Dtos = ObjectMapper.Map, List>(last5Expenses); + + // Dönüş DTO'su + return new ExpensesDto + { + TotalRequested = totalRequested, + TotalApproved = totalApproved, + Last5Expenses = last5Dtos + }; + } + + private async Task> GetTrainingsAsync() + { + var trainings = await _trainingRepository.GetListAsync(a => a.StartDate >= DateTime.Today); + + return ObjectMapper.Map, List>(trainings); + } + + private async Task> GetReservationsAsync() + { + var reservations = await _reservationRepository.GetListAsync(a => a.StartDate >= DateTime.Today); + + return ObjectMapper.Map, List>(reservations); + } + + private async Task> GetVisitorsAsync() + { + var visitors = await _visitorRepository.GetListAsync(a => a.VisitDate >= DateTime.Today); + + return ObjectMapper.Map, List>(visitors); + } + + private async Task> GetBirthdaysAsync() + { + var today = DateTime.Now; + + var employees = await _employeeRepository + .WithDetailsAsync(e => e.EmploymentType, e => e.JobPosition, e => e.Department) + .ContinueWith(t => t.Result + .Where(e => e.BirthDate.Day == today.Day && e.BirthDate.Month == today.Month) + .ToList()); + + return ObjectMapper.Map, List>(employees); + } + private async Task> GetUpcomingEventsAsync() { var events = await _eventRepository @@ -74,7 +243,7 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService Avatar = employee.Avatar }, Participants = evt.ParticipantsCount, - Photos = [], + Photos = [], Comments = [], Likes = evt.Likes, IsPublished = evt.isPublished diff --git a/api/src/Kurs.Platform.Application/Intranet/IntranetAutoMapperProfile.cs b/api/src/Kurs.Platform.Application/Intranet/IntranetAutoMapperProfile.cs new file mode 100644 index 00000000..0a5bc89c --- /dev/null +++ b/api/src/Kurs.Platform.Application/Intranet/IntranetAutoMapperProfile.cs @@ -0,0 +1,20 @@ +using AutoMapper; +using Kurs.Platform.Entities; + +namespace Kurs.Platform.Intranet; + +public class IntranetAutoMapperProfile : Profile +{ + public IntranetAutoMapperProfile() + { + CreateMap(); + CreateMap(); + CreateMap(); + CreateMap(); + CreateMap(); + CreateMap(); + CreateMap(); + CreateMap(); + CreateMap(); + } +} diff --git a/api/src/Kurs.Platform.DbMigrator/Seeds/ListFormSeeder.cs b/api/src/Kurs.Platform.DbMigrator/Seeds/ListFormSeeder.cs index 5cd71c33..bef2a3ae 100644 --- a/api/src/Kurs.Platform.DbMigrator/Seeds/ListFormSeeder.cs +++ b/api/src/Kurs.Platform.DbMigrator/Seeds/ListFormSeeder.cs @@ -41204,9 +41204,9 @@ public class ListFormSeeder : IDataSeedContributor, ITransientDependency RoleId = null, UserId = null, CultureName = LanguageCodes.En, - SourceDbType = DbType.Date, + SourceDbType = DbType.DateTime, FieldName = "VisitDate", - Width = 100, + Width = 150, ListOrderNo = 7, Visible = true, IsActive = true, diff --git a/api/src/Kurs.Platform.Domain/BlobStoring/BlobContainerNames.cs b/api/src/Kurs.Platform.Domain/BlobStoring/BlobContainerNames.cs index 6af2bf72..f780cc8d 100644 --- a/api/src/Kurs.Platform.Domain/BlobStoring/BlobContainerNames.cs +++ b/api/src/Kurs.Platform.Domain/BlobStoring/BlobContainerNames.cs @@ -2,6 +2,7 @@ namespace Kurs.Platform.BlobStoring; public static class BlobContainerNames { + public const string Intranet = "intranet"; public const string Avatar = "avatar"; public const string Import = "import"; public const string Activity = "activity"; diff --git a/api/src/Kurs.Platform.Domain/Entities/Tenant/Intranet/Visitor.cs b/api/src/Kurs.Platform.Domain/Entities/Tenant/Intranet/Visitor.cs index c2a1b960..f2223ba6 100644 --- a/api/src/Kurs.Platform.Domain/Entities/Tenant/Intranet/Visitor.cs +++ b/api/src/Kurs.Platform.Domain/Entities/Tenant/Intranet/Visitor.cs @@ -1,4 +1,3 @@ -// Domain/Entities/Visitor.cs using System; using Volo.Abp.Domain.Entities.Auditing; using Volo.Abp.MultiTenancy; @@ -17,7 +16,9 @@ public class Visitor : FullAuditedEntity, IMultiTenant public DateTime VisitDate { get; set; } public DateTime? CheckIn { get; set; } public DateTime? CheckOut { get; set; } - public Guid? EmployeeId { get; set; } // HrEmployee referansı + public Guid? EmployeeId { get; set; } public Employee Employee { get; set; } - public string Status { get; set; } //checked-in, checked-out, scheduled + public string Status { get; set; } + public string BadgeNumber { get; set; } + public string Photo { get; set; } } diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251028200151_Initial.Designer.cs b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251029074530_Initial.Designer.cs similarity index 99% rename from api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251028200151_Initial.Designer.cs rename to api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251029074530_Initial.Designer.cs index e0364783..030e6f14 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251028200151_Initial.Designer.cs +++ b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251029074530_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace Kurs.Platform.Migrations { [DbContext(typeof(PlatformDbContext))] - [Migration("20251028200151_Initial")] + [Migration("20251029074530_Initial")] partial class Initial { /// @@ -9058,6 +9058,9 @@ namespace Kurs.Platform.Migrations b.Property("Id") .HasColumnType("uniqueidentifier"); + b.Property("BadgeNumber") + .HasColumnType("nvarchar(max)"); + b.Property("CheckIn") .HasColumnType("datetime2"); @@ -9115,6 +9118,9 @@ namespace Kurs.Platform.Migrations .HasMaxLength(20) .HasColumnType("nvarchar(20)"); + b.Property("Photo") + .HasColumnType("nvarchar(max)"); + b.Property("Purpose") .HasMaxLength(250) .HasColumnType("nvarchar(250)"); diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251028200151_Initial.cs b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251029074530_Initial.cs similarity index 99% rename from api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251028200151_Initial.cs rename to api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251029074530_Initial.cs index 4a8433f2..3513fe45 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251028200151_Initial.cs +++ b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20251029074530_Initial.cs @@ -4462,6 +4462,8 @@ namespace Kurs.Platform.Migrations CheckOut = table.Column(type: "datetime2", nullable: true), EmployeeId = table.Column(type: "uniqueidentifier", nullable: true), Status = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), + BadgeNumber = table.Column(type: "nvarchar(max)", nullable: true), + Photo = table.Column(type: "nvarchar(max)", nullable: true), CreationTime = table.Column(type: "datetime2", nullable: false), CreatorId = table.Column(type: "uniqueidentifier", nullable: true), LastModificationTime = table.Column(type: "datetime2", nullable: true), diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs index c23e4a5a..9410a54d 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs +++ b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs @@ -9055,6 +9055,9 @@ namespace Kurs.Platform.Migrations b.Property("Id") .HasColumnType("uniqueidentifier"); + b.Property("BadgeNumber") + .HasColumnType("nvarchar(max)"); + b.Property("CheckIn") .HasColumnType("datetime2"); @@ -9112,6 +9115,9 @@ namespace Kurs.Platform.Migrations .HasMaxLength(20) .HasColumnType("nvarchar(20)"); + b.Property("Photo") + .HasColumnType("nvarchar(max)"); + b.Property("Purpose") .HasMaxLength(250) .HasColumnType("nvarchar(250)"); diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/Seeds/TenantData.json b/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/Seeds/TenantData.json index a576dd4d..759cb240 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/Seeds/TenantData.json +++ b/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/Seeds/TenantData.json @@ -100,9 +100,7 @@ "props": null, "description": null, "isActive": true, - "dependencies": [ - "AxiosListComponent" - ] + "dependencies": ["AxiosListComponent"] } ], "ReportCategories": [ @@ -1509,7 +1507,7 @@ { "CategoryName": "Spor", "TypeName": "Futbol Turnuvası", - "Date": "2025-11-15T10:00:00", + "Date": "2025-11-05T10:00:00", "Name": "Yaz Futbol Turnuvası 2025", "Description": "Tüm departmanların katılımıyla düzenlenen geleneksel yaz futbol turnuvası.", "Place": "Şirket Kampüsü Spor Alanı", @@ -1524,7 +1522,7 @@ { "CategoryName": "Kültür", "TypeName": "Kültürel Sanat Günü", - "Date": "2025-11-17T10:00:00", + "Date": "2025-11-01T10:00:00", "Name": "Kültür Gezisi: Kapadokya", "Description": "Çalışanlarımıza özel, rehber eşliğinde 2 günlük kültürel gezi.", "Place": "Kapadokya, Nevşehir", @@ -1539,7 +1537,7 @@ { "CategoryName": "Müzik", "TypeName": "Caz Akşamı", - "Date": "2025-11-18T10:00:00", + "Date": "2025-11-09T10:00:00", "Name": "Müzik Dinletisi: Jazz Akşamı", "Description": "Caz müziğinin en güzel örneklerinin canlı performanslarla sunulacağı özel akşam.", "Place": "Şirket Konferans Salonu", @@ -2408,12 +2406,7 @@ "minSalary": 80000, "maxSalary": 120000, "currencyCode": "USD", - "requiredSkills": [ - "JavaScript", - "TypeScript", - "React", - "Node.js" - ], + "requiredSkills": ["JavaScript", "TypeScript", "React", "Node.js"], "responsibilities": [ "Develop frontend and backend applications", "Write clean and maintainable code", @@ -2858,7 +2851,7 @@ "fullName": "Ali Öztürk", "avatar": "https://i.pravatar.cc/150?img=12", "nationalId": "12345678901", - "birthDate": "10-10-1988", + "birthDate": "2020-10-29", "gender": "Male", "maritalStatus": "Married", "country": "TR", @@ -2898,7 +2891,7 @@ "fullName": "Ayşe Kaya", "avatar": "https://i.pravatar.cc/150?img=5", "nationalId": "12345678902", - "birthDate": "02-08-1990", + "birthDate": "2015-10-30", "gender": "Female", "maritalStatus": "Single", "country": "TR", @@ -2938,7 +2931,7 @@ "fullName": "Mehmet Yılmaz", "avatar": "https://i.pravatar.cc/150?img=8", "nationalId": "12345678903", - "birthDate": "12-03-1987", + "birthDate": "2010-10-31", "gender": "Male", "maritalStatus": "Married", "country": "TR", @@ -2978,7 +2971,7 @@ "fullName": "Selin Demir", "avatar": "https://i.pravatar.cc/150?img=9", "nationalId": "12345678904", - "birthDate": "05-05-1993", + "birthDate": "2000-11-01", "gender": "Female", "maritalStatus": "Single", "country": "TR", @@ -3018,7 +3011,7 @@ "fullName": "Ahmet Çelik", "avatar": "https://i.pravatar.cc/150?img=33", "nationalId": "12345678905", - "birthDate": "10-09-1985", + "birthDate": "1999-11-02", "gender": "Male", "maritalStatus": "Married", "country": "TR", @@ -3058,7 +3051,7 @@ "fullName": "Zeynep Arslan", "avatar": "https://i.pravatar.cc/150?img=10", "nationalId": "12345678906", - "birthDate": "01-01-1995", + "birthDate": "1995-11-03", "gender": "Female", "maritalStatus": "Single", "country": "TR", @@ -3098,7 +3091,7 @@ "fullName": "Burak Koç", "avatar": "https://i.pravatar.cc/150?img=14", "nationalId": "12345678907", - "birthDate": "08-06-1991", + "birthDate": "1980-11-04", "gender": "Male", "maritalStatus": "Married", "country": "TR", @@ -3138,7 +3131,7 @@ "fullName": "Elif Şahin", "avatar": "https://i.pravatar.cc/150?img=20", "nationalId": "12345678908", - "birthDate": "05-11-1989", + "birthDate": "1989-11-05", "gender": "Female", "maritalStatus": "Married", "country": "TR", @@ -3178,7 +3171,7 @@ "fullName": "Canan Öztürk", "avatar": "https://i.pravatar.cc/150?img=25", "nationalId": "12345678909", - "birthDate": "04-04-1992", + "birthDate": "1992-11-06", "gender": "Female", "maritalStatus": "Single", "country": "TR", @@ -3218,7 +3211,7 @@ "fullName": "Murat Aydın", "avatar": "https://i.pravatar.cc/150?img=30", "nationalId": "12345678910", - "birthDate": "03-12-1984", + "birthDate": "1984-11-07", "gender": "Male", "maritalStatus": "Married", "country": "TR", @@ -3561,8 +3554,8 @@ "category": "technical", "type": "online", "duration": 16, - "startDate": "01-11-2024", - "endDate": "08-11-2024", + "startDate": "2025-10-29", + "endDate": "2025-11-08", "maxParticipants": 20, "enrolled": 15, "status": "upcoming", @@ -3576,8 +3569,8 @@ "category": "soft-skills", "type": "classroom", "duration": 8, - "startDate": "05-10-2024", - "endDate": "05-10-2024", + "startDate": "2024-11-05", + "endDate": "2024-12-05", "maxParticipants": 15, "enrolled": 12, "status": "ongoing", @@ -3591,8 +3584,8 @@ "category": "management", "type": "hybrid", "duration": 24, - "startDate": "10-09-2024", - "endDate": "03-09-2024", + "startDate": "2025-10-29", + "endDate": "2025-11-03", "maxParticipants": 25, "enrolled": 25, "status": "completed", @@ -3606,8 +3599,8 @@ "category": "compliance", "type": "online", "duration": 12, - "startDate": "05-11-2024", - "endDate": "02-11-2024", + "startDate": "2024-11-05", + "endDate": "2024-11-30", "maxParticipants": 50, "enrolled": 8, "status": "upcoming", @@ -3620,8 +3613,8 @@ "type": "room", "resourceName": "Toplantı Salonu A", "employeeCode": "EMP-001", - "startDate": "10-10-2024 09:00:00", - "endDate": "10-10-2024 11:00:00", + "startDate": "10-10-2025 09:00:00", + "endDate": "10-10-2025 11:00:00", "purpose": "Sprint Planning Toplantısı", "status": "approved", "participants": 8, @@ -3631,8 +3624,8 @@ "type": "vehicle", "resourceName": "Şirket Aracı - 34 ABC 123", "employeeCode": "EMP-001", - "startDate": "11-10-2024 08:00:00", - "endDate": "11-10-2024 18:00:00", + "startDate": "11-10-2025 08:00:00", + "endDate": "11-10-2025 18:00:00", "purpose": "Müşteri Ziyareti", "status": "pending", "notes": "Ankara çıkışı" @@ -3641,8 +3634,8 @@ "type": "equipment", "resourceName": "Kamera ve Tripod Seti", "employeeCode": "EMP-001", - "startDate": "09-10-2024 14:00:00", - "endDate": "09-10-2024 17:00:00", + "startDate": "09-10-2025 14:00:00", + "endDate": "09-10-2025 17:00:00", "purpose": "Ürün Tanıtım Videosu Çekimi", "status": "approved" }, @@ -3650,8 +3643,8 @@ "type": "room", "resourceName": "Eğitim Salonu B", "employeeCode": "EMP-001", - "startDate": "05-10-2024 09:00:00", - "endDate": "05-10-2024 17:00:00", + "startDate": "05-10-2025 09:00:00", + "endDate": "05-10-2025 17:00:00", "purpose": "Etkili İletişim Eğitimi", "status": "approved", "participants": 15, @@ -3783,33 +3776,36 @@ "companyName": "ABC Teknoloji", "email": "ali.veli@abc.com", "phone": "5321112233", - "visitDate": "2025-10-05", - "checkIn": "05-10-2025", + "visitDate": "2025-10-29T09:00:00", + "checkIn": "2025-10-29T09:15:00", "employeeCode": "EMP-001", "purpose": "İş Ortaklığı Görüşmesi", - "status": "checked-in" + "status": "checked-in", + "photo": "https://i.pravatar.cc/150?img=12" }, { "fullName": "Fatma Yıldız", "companyName": "XYZ Danışmanlık", "email": "fatma@xyz.com", "phone": "5332223344", - "visitDate": "01-10-2024", + "visitDate": "2025-10-30T10:30:00", "employeeCode": "EMP-002", "purpose": "Eğitim Danışmanlığı", - "status": "scheduled" + "status": "scheduled", + "photo": "https://i.pravatar.cc/150?img=13" }, { "fullName": "Mehmet Kara", "companyName": "DEF Yazılım", "email": "mehmet@def.com", "phone": "5343334455", - "visitDate": "08-10-2024", - "checkIn": "08-10-2024", - "checkOut": "10-10-2024", + "visitDate": "2025-10-31T14:00:00", + "checkIn": "2025-10-31T14:10:00", + "checkOut": "2025-10-31T16:00:00", "employeeCode": "EMP-003", "purpose": "Teknik Sunum", - "status": "checked-out" + "status": "checked-out", + "photo": "https://i.pravatar.cc/150?img=14" } ], "ExpenseRequests": [ @@ -3818,19 +3814,19 @@ "category": "travel", "amount": 850, "currencyCode": "TRY", - "requestDate": "08-10-2024", + "requestDate": "2025-10-04", "description": "Ankara ofis ziyareti - uçak bileti", "project": "Intranet v2", "status": "approved", "approverCode": "EMP-004", - "approvalDate": "10-10-2024" + "approvalDate": "2025-10-10" }, { "employeeCode": "EMP-002", "category": "meal", "amount": 320, "currencyCode": "TRY", - "requestDate": "07-10-2024", + "requestDate": "2025-10-07", "description": "Müşteri toplantısı - öğle yemeği", "project": null, "status": "pending", @@ -3842,12 +3838,12 @@ "category": "accommodation", "amount": 1200, "currencyCode": "TRY", - "requestDate": "04-10-2024", + "requestDate": "2025-10-04", "description": "İzmir workshop - otel konaklaması (2 gece)", "project": "UX Workshop", "status": "approved", "approverCode": "EMP-005", - "approvalDate": "05-10-2024" + "approvalDate": "2025-10-05" } ], "Surveys": [ @@ -4105,9 +4101,7 @@ { "postContent": "CI/CD pipeline güncellememiz tamamlandı! Deployment süremiz %40 azaldı. Otomasyonun gücü 💪", "type": "video", - "urls": [ - "https://www.w3schools.com/html/mov_bbb.mp4" - ] + "urls": ["https://www.w3schools.com/html/mov_bbb.mp4"] } ], "SocialPollOptions": [ @@ -4188,4 +4182,4 @@ "employeeCode": "EMP-003" } ] -} \ No newline at end of file +} diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/TenantDataSeeder.cs b/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/TenantDataSeeder.cs index 4be0018d..c1364b74 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/TenantDataSeeder.cs +++ b/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/TenantDataSeeder.cs @@ -1293,7 +1293,8 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency CheckIn = item.CheckIn, CheckOut = item.CheckOut, EmployeeId = employee != null ? employee.Id : null, - Status = item.Status + Status = item.Status, + Photo = item.Photo }); } diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/TenantSeederDto.cs b/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/TenantSeederDto.cs index a43b410e..56ee6377 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/TenantSeederDto.cs +++ b/api/src/Kurs.Platform.EntityFrameworkCore/Tenants/TenantSeederDto.cs @@ -201,6 +201,7 @@ public class VisitorSeedDto public DateTime? CheckOut { get; set; } public string EmployeeCode { get; set; } public string Status { get; set; } + public string Photo { get; set; } } public class AnnouncementSeedDto diff --git a/ui/src/components/common/MultiSelectEmployee.tsx b/ui/src/components/common/MultiSelectEmployee.tsx index 3e6d5aee..da20d6f1 100644 --- a/ui/src/components/common/MultiSelectEmployee.tsx +++ b/ui/src/components/common/MultiSelectEmployee.tsx @@ -1,7 +1,7 @@ import React, { useState, useRef, useEffect } from "react"; import { FaChevronDown, FaTimes } from "react-icons/fa"; import { mockEmployees } from "../../mocks/mockEmployees"; -import { HrEmployee } from "../../types/hr"; +import { EmployeeDto } from "../../types/hr"; interface MultiSelectEmployeeProps { selectedEmployees: string[]; @@ -40,7 +40,7 @@ const MultiSelectEmployee: React.FC = ({ }, []); const filteredEmployees = mockEmployees.filter( - (employee: HrEmployee) => + (employee: EmployeeDto) => employee.fullName.toLowerCase().includes(searchTerm.toLowerCase()) || employee.code.toLowerCase().includes(searchTerm.toLowerCase()) ); @@ -58,7 +58,7 @@ const MultiSelectEmployee: React.FC = ({ onChange(selectedEmployees.filter((id) => id !== employeeId)); }; - const selectedEmployeeObjects = mockEmployees.filter((emp: HrEmployee) => + const selectedEmployeeObjects = mockEmployees.filter((emp: EmployeeDto) => selectedEmployees.includes(emp.id) ); @@ -79,7 +79,7 @@ const MultiSelectEmployee: React.FC = ({
{selectedEmployeeObjects.length > 0 ? ( - selectedEmployeeObjects.map((employee: HrEmployee) => ( + selectedEmployeeObjects.map((employee: EmployeeDto) => ( = ({ {/* Options List */}
{filteredEmployees.length > 0 ? ( - filteredEmployees.map((employee: HrEmployee) => ( + filteredEmployees.map((employee: EmployeeDto) => (
void; - onSave: (department: Partial) => void; - department?: HrDepartment; + onSave: (department: Partial) => void; + department?: DepartmentDto; title: string; } diff --git a/ui/src/views/hr/components/DepartmentManagement.tsx b/ui/src/views/hr/components/DepartmentManagement.tsx index ecd849a7..30f2c7cb 100644 --- a/ui/src/views/hr/components/DepartmentManagement.tsx +++ b/ui/src/views/hr/components/DepartmentManagement.tsx @@ -10,7 +10,7 @@ import { FaList, FaTh, } from 'react-icons/fa' -import { HrDepartment } from '../../../types/hr' +import { DepartmentDto } from '../../../types/hr' import DataTable, { Column } from '../../../components/common/DataTable' import { mockDepartments } from '../../../mocks/mockDepartments' import { mockEmployees } from '../../../mocks/mockEmployees' @@ -21,7 +21,7 @@ import Widget from '../../../components/common/Widget' import { Container } from '@/components/shared' const DepartmentManagement: React.FC = () => { - const [departments, setDepartments] = useState(mockDepartments) + const [departments, setDepartments] = useState(mockDepartments) const [searchTerm, setSearchTerm] = useState('') const [selectedParent, setSelectedParent] = useState('all') const [viewMode, setViewMode] = useState<'list' | 'cards'>('list') @@ -29,7 +29,7 @@ const DepartmentManagement: React.FC = () => { // Modal states const [isFormModalOpen, setIsFormModalOpen] = useState(false) const [isViewModalOpen, setIsViewModalOpen] = useState(false) - const [selectedDepartment, setSelectedDepartment] = useState() + const [selectedDepartment, setSelectedDepartment] = useState() const [modalTitle, setModalTitle] = useState('') const handleAdd = () => { @@ -38,13 +38,13 @@ const DepartmentManagement: React.FC = () => { setIsFormModalOpen(true) } - const handleEdit = (department: HrDepartment) => { + const handleEdit = (department: DepartmentDto) => { setSelectedDepartment(department) setModalTitle('Departman Düzenle') setIsFormModalOpen(true) } - const handleView = (department: HrDepartment) => { + const handleView = (department: DepartmentDto) => { setSelectedDepartment(department) setIsViewModalOpen(true) } @@ -55,7 +55,7 @@ const DepartmentManagement: React.FC = () => { } } - const handleSave = (departmentData: Partial) => { + const handleSave = (departmentData: Partial) => { if (selectedDepartment) { // Edit existing department setDepartments((prev) => @@ -67,13 +67,13 @@ const DepartmentManagement: React.FC = () => { ) } else { // Add new department - const newDepartment: HrDepartment = { + const newDepartment: DepartmentDto = { id: `dept_${Date.now()}`, ...departmentData, subDepartments: [], creationTime: new Date(), lastModificationTime: new Date(), - } as HrDepartment + } as DepartmentDto setDepartments((prev) => [...prev, newDepartment]) } setIsFormModalOpen(false) @@ -89,7 +89,7 @@ const DepartmentManagement: React.FC = () => { setSelectedDepartment(undefined) } - const handleEditFromView = (department: HrDepartment) => { + const handleEditFromView = (department: DepartmentDto) => { setIsViewModalOpen(false) handleEdit(department) } @@ -117,7 +117,7 @@ const DepartmentManagement: React.FC = () => { return true }) - const columns: Column[] = [ + const columns: Column[] = [ { key: 'code', header: 'Departman Kodu', @@ -131,17 +131,17 @@ const DepartmentManagement: React.FC = () => { { key: 'parentDepartment', header: 'Üst Departman', - render: (department: HrDepartment) => department.parentDepartment?.name || '-', + render: (department: DepartmentDto) => department.parentDepartment?.name || '-', }, { key: 'manager', header: 'Yönetici', - render: (department: HrDepartment) => department.manager?.fullName || '-', + render: (department: DepartmentDto) => department.manager?.fullName || '-', }, { key: 'employeeCount', header: 'Personel Sayısı', - render: (department: HrDepartment) => ( + render: (department: DepartmentDto) => (
{mockEmployees.filter((a) => a.departmentId == department.id).length || 0} @@ -151,7 +151,7 @@ const DepartmentManagement: React.FC = () => { { key: 'costCenter', header: 'Maliyet Merkezi', - render: (department: HrDepartment) => ( + render: (department: DepartmentDto) => (
{department.costCenter?.code || '-'} {department.costCenter?.name || '-'} @@ -161,7 +161,7 @@ const DepartmentManagement: React.FC = () => { { key: 'budget', header: 'Bütçe', - render: (department: HrDepartment) => ( + render: (department: DepartmentDto) => (
{department.budget ? `₺${department.budget.toLocaleString()}` : '-'} @@ -171,7 +171,7 @@ const DepartmentManagement: React.FC = () => { { key: 'status', header: 'Durum', - render: (department: HrDepartment) => ( + render: (department: DepartmentDto) => ( { { key: 'actions', header: 'İşlemler', - render: (department: HrDepartment) => ( + render: (department: DepartmentDto) => (