diff --git a/README.md b/README.md index a21adcbb..059a0cde 100644 --- a/README.md +++ b/README.md @@ -289,7 +289,6 @@ Seed edilen host yöneticisi ile `/login` üzerinden giriş yapılır (kullanıc | `VITE_CDN_URL` | Dosya/CDN kök adresi. | | `VITE_REACT_APP_VERSION` | `package.json` sürümünden beslenir. | | `VITE_AI_URL` | AI asistanının n8n webhook kökü. | -| `VITE_GOOGLE_MAPS_API_KEY` | Harita bileşenleri için anahtar. | | `VITE_USE_POLLING` | Dosya izlemede polling (WSL/Docker senaryoları). | | `VITE_PWA_DEV` | Geliştirmede service worker'ı açar (varsayılan kapalı). | diff --git a/api/src/Sozsoft.Platform.Application.Contracts/Intranet/CreateSocialPostInput.cs b/api/src/Sozsoft.Platform.Application.Contracts/Intranet/CreateSocialPostInput.cs index e8afd73e..431dd3c9 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/Intranet/CreateSocialPostInput.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/Intranet/CreateSocialPostInput.cs @@ -6,11 +6,6 @@ public class CreateSocialPostInput { public string Content { get; set; } = string.Empty; - /// - /// JSON string containing location data (name, address, lat, lng, placeId). - /// - public string? LocationJson { get; set; } - public CreateSocialPostMediaInput? Media { get; set; } } diff --git a/api/src/Sozsoft.Platform.Application.Contracts/Intranet/SocialPostDto.cs b/api/src/Sozsoft.Platform.Application.Contracts/Intranet/SocialPostDto.cs index 891a3686..42b8f592 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/Intranet/SocialPostDto.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/Intranet/SocialPostDto.cs @@ -15,22 +15,11 @@ public class SocialPostDto : FullAuditedEntityDto public bool IsLiked { get; set; } public bool IsOwnPost { get; set; } - public SocialLocationDto? Location { get; set; } public SocialMediaDto? Media { get; set; } public List Comments { get; set; } = []; public List Likes { get; set; } = []; } -public class SocialLocationDto : FullAuditedEntityDto -{ - public Guid SocialPostId { get; set; } - public string Name { get; set; } = string.Empty; - public string? Address { get; set; } - public double? Lat { get; set; } - public double? Lng { get; set; } - public string? PlaceId { get; set; } -} - public class SocialMediaDto : FullAuditedEntityDto { public Guid SocialPostId { get; set; } diff --git a/api/src/Sozsoft.Platform.Application/Intranet/IntranetAppService.cs b/api/src/Sozsoft.Platform.Application/Intranet/IntranetAppService.cs index f122a6e2..1f699298 100644 --- a/api/src/Sozsoft.Platform.Application/Intranet/IntranetAppService.cs +++ b/api/src/Sozsoft.Platform.Application/Intranet/IntranetAppService.cs @@ -620,7 +620,7 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService // Sonra sadece bu ID'ler için detayları yükle var queryable = await _socialPostRepository - .WithDetailsAsync(e => e.Location, e => e.Media, e => e.Comments, e => e.Likes); + .WithDetailsAsync(e => e.Media, e => e.Comments, e => e.Likes); var socialPosts = await AsyncExecuter.ToListAsync( queryable @@ -852,20 +852,6 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService Content = input.Content, }; - if (!string.IsNullOrWhiteSpace(input.LocationJson)) - { - var locData = System.Text.Json.JsonSerializer.Deserialize(input.LocationJson); - post.Location = new SocialLocation(Guid.NewGuid()) - { - SocialPostId = post.Id, - Name = locData.TryGetProperty("name", out var nameProp) ? nameProp.GetString() ?? string.Empty : string.Empty, - Address = locData.TryGetProperty("address", out var addrProp) ? addrProp.GetString() : null, - Lat = locData.TryGetProperty("lat", out var latProp) && latProp.TryGetDouble(out var latVal) ? latVal : null, - Lng = locData.TryGetProperty("lng", out var lngProp) && lngProp.TryGetDouble(out var lngVal) ? lngVal : null, - PlaceId = locData.TryGetProperty("placeId", out var placeIdProp) ? placeIdProp.GetString() : null, - }; - } - if (input.Media != null) { var media = new SocialMedia(Guid.NewGuid()) @@ -895,7 +881,7 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService // Reload with full navigation properties for mapping var queryable = await _socialPostRepository - .WithDetailsAsync(e => e.Location, e => e.Media, e => e.Comments, e => e.Likes); + .WithDetailsAsync(e => e.Media, e => e.Comments, e => e.Likes); var savedPost = await AsyncExecuter.FirstOrDefaultAsync(queryable.Where(p => p.Id == post.Id)); var dto = ObjectMapper.Map(savedPost!); @@ -956,7 +942,7 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService await _socialPostRepository.UpdateAsync(post, autoSave: true); var queryable = await _socialPostRepository - .WithDetailsAsync(e => e.Location, e => e.Media, e => e.Comments, e => e.Likes); + .WithDetailsAsync(e => e.Media, e => e.Comments, e => e.Likes); var updated = await AsyncExecuter.FirstOrDefaultAsync(queryable.Where(p => p.Id == id)); var dto = ObjectMapper.Map(updated!); diff --git a/api/src/Sozsoft.Platform.Application/Intranet/IntranetMappers.cs b/api/src/Sozsoft.Platform.Application/Intranet/IntranetMappers.cs index a4272efa..45538aa3 100644 --- a/api/src/Sozsoft.Platform.Application/Intranet/IntranetMappers.cs +++ b/api/src/Sozsoft.Platform.Application/Intranet/IntranetMappers.cs @@ -116,22 +116,6 @@ public partial class SocialPostToSocialPostDtoMapper : MapperBase -{ - public override partial SocialLocationDto Map(SocialLocation source); - - public override partial void Map(SocialLocation source, SocialLocationDto destination); - - public override void BeforeMap(SocialLocation source) - { - } - - public override void AfterMap(SocialLocation source, SocialLocationDto destination) - { - } -} - [Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)] public partial class SocialMediaToSocialMediaDtoMapper : MapperBase { diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json index 5f121fd9..a7184777 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json @@ -12852,120 +12852,6 @@ "tr": "Tüm gönderiler yüklendi", "en": "All posts loaded" }, - { - "resourceName": "Platform", - "key": "App.Platform.Intranet.SocialWall.LocationMap.OpenInGoogleMaps", - "en": "Open in Google Maps", - "tr": "Google Maps'te aç" - }, - { - "resourceName": "Platform", - "key": "App.Platform.Intranet.SocialWall.LocationMap.ClickForDirections", - "en": "Click to get directions", - "tr": "Yol tarifi almak için tıklayın" - }, - { - "resourceName": "Platform", - "key": "App.Platform.Intranet.SocialWall.LocationPicker.ApiKeyError", - "en": "Google Maps API key not found. Please add VITE_GOOGLE_MAPS_API_KEY to your .env file.", - "tr": "Google Maps API anahtarı bulunamadı. Lütfen .env dosyasına VITE_GOOGLE_MAPS_API_KEY ekleyin." - }, - { - "resourceName": "Platform", - "key": "App.Platform.Intranet.SocialWall.LocationPicker.GoogleMapsLoadError", - "en": "Google Maps could not be loaded. Please check your internet connection.", - "tr": "Google Maps yüklenemedi. Lütfen internet bağlantınızı kontrol edin." - }, - { - "resourceName": "Platform", - "key": "App.Platform.Intranet.SocialWall.LocationPicker.NoResults", - "en": "No results found", - "tr": "Sonuç bulunamadı" - }, - { - "resourceName": "Platform", - "key": "App.Platform.Intranet.SocialWall.LocationPicker.OverQueryLimit", - "en": "Google Places query limit exceeded. Please try again later.", - "tr": "Google Places sorgu limiti aşıldı. Lütfen daha sonra tekrar deneyiniz." - }, - { - "resourceName": "Platform", - "key": "App.Platform.Intranet.SocialWall.LocationPicker.RequestDenied", - "en": "Google Places request denied. Please check your API key, billing, or permissions settings.", - "tr": "Google Places isteği reddedildi. API key, billing veya yetki ayarlarını kontrol ediniz." - }, - { - "resourceName": "Platform", - "key": "App.Platform.Intranet.SocialWall.LocationPicker.InvalidRequest", - "en": "Invalid location search request. Please check your input and try again.", - "tr": "Geçersiz konum arama isteği." - }, - { - "resourceName": "Platform", - "key": "App.Platform.Intranet.SocialWall.LocationPicker.UnknownError", - "en": "Google Places returned a temporary error. Please try again.", - "tr": "Google Places geçici bir hata döndürdü. Lütfen tekrar deneyiniz." - }, - { - "resourceName": "Platform", - "key": "App.Platform.Intranet.SocialWall.LocationPicker.SearchFailed", - "en": "Location search failed", - "tr": "Konum arama başarısız" - }, - { - "resourceName": "Platform", - "key": "App.Platform.Intranet.SocialWall.LocationPicker.SearchError", - "en": "An error occurred during location search", - "tr": "Konum arama sırasında bir hata oluştu" - }, - { - "resourceName": "Platform", - "key": "App.Platform.Intranet.SocialWall.LocationPicker.AddLocation", - "en": "Add Location", - "tr": "Konum Ekle" - }, - { - "resourceName": "Platform", - "key": "App.Platform.Intranet.SocialWall.LocationPicker.SearchPlaceholder", - "en": "Search location...", - "tr": "Konum ara..." - }, - { - "resourceName": "Platform", - "key": "App.Platform.Intranet.SocialWall.LocationPicker.LoadingGoogleMaps", - "en": "Loading Google Maps...", - "tr": "Google Maps yükleniyor..." - }, - { - "resourceName": "Platform", - "key": "App.Platform.Intranet.SocialWall.LocationPicker.SearchingLocations", - "en": "Searching locations...", - "tr": "Konumlar aranıyor..." - }, - { - "resourceName": "Platform", - "key": "App.Platform.Intranet.SocialWall.LocationPicker.TypeToSearch", - "en": "Type the location you want to search", - "tr": "Aramak istediğiniz konumu yazın" - }, - { - "resourceName": "Platform", - "key": "App.Platform.Intranet.SocialWall.LocationPicker.Example", - "en": "e.g. Taksim, Istanbul", - "tr": "Örn: Taksim, İstanbul" - }, - { - "resourceName": "Platform", - "key": "App.Platform.Intranet.SocialWall.LocationPicker.NotFound", - "en": "Location not found. Try a different search.", - "tr": "Konum bulunamadı. Farklı bir arama yapın." - }, - { - "resourceName": "Platform", - "key": "App.Platform.Intranet.SocialWall.LocationPicker.SelectLocation", - "en": "Select a location", - "tr": "Bir konum seçin" - }, { "resourceName": "Platform", "key": "App.Platform.Intranet.SocialWall.MediaManager.AddMedia", @@ -13068,6 +12954,30 @@ "en": "Write a comment...", "tr": "Yorum yazın..." }, + { + "resourceName": "Platform", + "key": "App.Platform.Intranet.SocialWall.PostItem.VoteCount", + "en": "{count} votes", + "tr": "{count} oy" + }, + { + "resourceName": "Platform", + "key": "App.Platform.Intranet.SocialWall.PostItem.PollEnded", + "en": "Ended", + "tr": "Sona erdi" + }, + { + "resourceName": "Platform", + "key": "App.Platform.Intranet.SocialWall.PostItem.PollEndsIn", + "en": "ends in {time}", + "tr": "{time} içinde bitiyor" + }, + { + "resourceName": "Platform", + "key": "App.Platform.Intranet.SocialWall.PostItem.Fullscreen", + "en": "Fullscreen", + "tr": "Tam ekran" + }, { "resourceName": "Platform", "key": "App.Platform.Intranet.Widgets.PriorityTasks.Title", @@ -13248,18 +13158,6 @@ "tr": "Yanıtlar", "en": "Responses" }, - { - "resourceName": "Platform", - "key": "App.Platform.Intranet.Widgets.ActiveSurveys.Duration", - "tr": "Süre", - "en": "Duration" - }, - { - "resourceName": "Platform", - "key": "App.Platform.Intranet.Widgets.ActiveSurveys.CompletionRate", - "tr": "Tamamlanma oranı", - "en": "Completion Rate" - }, { "resourceName": "Platform", "key": "App.Platform.Intranet.Widgets.ActiveSurveys.FillSurvey", @@ -13644,12 +13542,6 @@ "tr": "Tüm medyaları kaldır", "en": "Remove all media" }, - { - "resourceName": "Platform", - "key": "App.Platform.Intranet.SocialWall.CreatePost.RemoveLocationTitle", - "tr": "Konumu kaldır", - "en": "Remove location" - }, { "resourceName": "Platform", "key": "App.Platform.Intranet.SocialWall.CreatePost.Poll", @@ -13704,18 +13596,6 @@ "tr": "Emoji ekle", "en": "Add emoji" }, - { - "resourceName": "Platform", - "key": "App.Platform.Intranet.SocialWall.CreatePost.EditLocationTitle", - "tr": "Konumu değiştir", - "en": "Edit location" - }, - { - "resourceName": "Platform", - "key": "App.Platform.Intranet.SocialWall.CreatePost.AddLocationTitle", - "tr": "Konum ekle", - "en": "Add location" - }, { "resourceName": "Platform", "key": "App.Platform.Intranet.SocialWall.CreatePost.Submit", diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260808210009_Initial.Designer.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260811131124_Initial.Designer.cs similarity index 99% rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260808210009_Initial.Designer.cs rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260811131124_Initial.Designer.cs index 6db05ba3..e83dbf9c 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260808210009_Initial.Designer.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260811131124_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace Sozsoft.Platform.Migrations { [DbContext(typeof(PlatformDbContext))] - [Migration("20260808210009_Initial")] + [Migration("20260811131124_Initial")] partial class Initial { /// diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260808210009_Initial.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260811131124_Initial.cs similarity index 100% rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260808210009_Initial.cs rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260811131124_Initial.cs diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantDataSeeder.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantDataSeeder.cs index 36009cb8..00de524d 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantDataSeeder.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantDataSeeder.cs @@ -63,7 +63,6 @@ public class TenantSeederDto public List SurveyQuestions { get; set; } public List SurveyQuestionOptions { get; set; } public List SocialPosts { get; set; } - public List SocialLocations { get; set; } public List SocialMedias { get; set; } public List SocialPollOptions { get; set; } public List SocialComments { get; set; } @@ -97,16 +96,6 @@ public class SocialPostSeedDto public bool IsOwnPost { get; set; } } -public class SocialLocationSeedDto -{ - public string PostContent { 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 SocialMediaSeedDto { public string PostContent { get; set; } @@ -521,7 +510,6 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency private readonly IRepository _surveyQuestionRepository; private readonly IRepository _surveyQuestionOptionRepository; private readonly IRepository _socialPostRepository; - private readonly IRepository _socialLocationRepository; private readonly IRepository _socialMediaRepository; private readonly IRepository _socialPollOptionRepository; private readonly IRepository _socialCommentRepository; @@ -571,7 +559,6 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency IRepository surveyQuestionRepository, IRepository surveyQuestionOptionRepository, IRepository socialPostRepository, - IRepository socialLocationRepository, IRepository socialMediaRepository, IRepository socialPollOptionRepository, IRepository socialCommentRepository, @@ -620,7 +607,6 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency _surveyQuestionRepository = surveyQuestionRepository; _surveyQuestionOptionRepository = surveyQuestionOptionRepository; _socialPostRepository = socialPostRepository; - _socialLocationRepository = socialLocationRepository; _socialMediaRepository = socialMediaRepository; _socialPollOptionRepository = socialPollOptionRepository; _socialCommentRepository = socialCommentRepository; @@ -1162,28 +1148,6 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency }, autoSave: true); } - foreach (var item in items.SocialLocations) - { - var post = await _socialPostRepository.FirstOrDefaultAsync(x => x.Content == item.PostContent); - - if (post == null) - continue; - - var exists = await _socialLocationRepository.AnyAsync(x => x.SocialPostId == post.Id && x.Name == item.Name); - if (exists) - continue; - - await _socialLocationRepository.InsertAsync(new SocialLocation(Guid.NewGuid()) - { - SocialPostId = post != null ? post.Id : Guid.Empty, - Name = item.Name, - Address = item.Address, - Lat = item.Lat, - Lng = item.Lng, - PlaceId = item.PlaceId - }, autoSave: true); - } - foreach (var item in items.SocialMedias) { var post = await _socialPostRepository.FirstOrDefaultAsync(x => x.Content == item.PostContent); diff --git a/ui/.env b/ui/.env index 7126d16d..ed93409f 100644 --- a/ui/.env +++ b/ui/.env @@ -2,21 +2,3 @@ VITE_API_URL='https://localhost:44344' VITE_CDN_URL='http://localhost:4005' VITE_REACT_APP_VERSION=$npm_package_version VITE_AI_URL='https://ai.sozsoft.com/webhook/' -VITE_GOOGLE_MAPS_API_KEY='AIzaSyAefS2rvF-xwq7OHpZ27UYxXPbMo6OwACc' -# Google Cloud Console’da: - -# APIs & Services > Enabled APIs & services bölümüne gir. -# Şunların aktif olduğundan emin ol: -# Maps JavaScript API -# Places API - -# Eğer ileride koordinat/adres çözümleme yapıyorsan ayrıca: - -# Geocoding API -# APIs & Services > Credentials > API Key içine gir. -# API restrictions bölümünde: - -# Eğer Restrict key seçiliyse, izinli API listesine şunları ekle: - -# Maps JavaScript API -# Places API diff --git a/ui/src/proxy/intranet/models.ts b/ui/src/proxy/intranet/models.ts index ce117f5f..16ecfa5a 100644 --- a/ui/src/proxy/intranet/models.ts +++ b/ui/src/proxy/intranet/models.ts @@ -170,7 +170,6 @@ export interface SocialPostDto { id: string user: UserInfoViewModel content: string - locationJson?: string media?: SocialMediaDto likeCount: number isLiked: boolean diff --git a/ui/src/services/intranet.service.ts b/ui/src/services/intranet.service.ts index b125ad8b..99c2fdcd 100644 --- a/ui/src/services/intranet.service.ts +++ b/ui/src/services/intranet.service.ts @@ -11,7 +11,6 @@ import apiService, { Config } from './api.service' export interface CreateSocialPostInput { content: string - locationJson?: string media?: { type: 'image' | 'video' | 'poll' urls?: string[] diff --git a/ui/src/views/intranet/Dashboard.tsx b/ui/src/views/intranet/Dashboard.tsx index fe0f232b..d4dfeccb 100644 --- a/ui/src/views/intranet/Dashboard.tsx +++ b/ui/src/views/intranet/Dashboard.tsx @@ -23,7 +23,6 @@ import { useLocalization } from '@/utils/hooks/useLocalization' import useLocale from '@/utils/hooks/useLocale' import { currentLocalDate } from '@/utils/dateUtils' import { useStoreActions, useStoreState } from '@/store/store' -import type { DashboardLayout } from '@/store/admin.model' import Button from '@/components/ui/Button' import { LuX } from 'react-icons/lu' import type { DashboardWidgetColumn, DashboardWidgetDefinition } from './dashboardWidget' @@ -73,8 +72,6 @@ const layoutPresets = [ }, ] as const -type DashboardLayoutId = DashboardLayout - const columnSpanClasses: Record = { 2: 'lg:col-span-2', 3: 'lg:col-span-3', @@ -105,9 +102,13 @@ const IntranetDashboard: React.FC = () => { const currentLocale = useLocale() const fetchIntranetDashboard = async () => { - const dashboard = await intranetService.getDashboard() - if (dashboard.data) { - setIntranetDashboard(dashboard.data) + try { + const dashboard = await intranetService.getDashboard() + if (dashboard.data) { + setIntranetDashboard(dashboard.data) + } + } catch { + // hata apiService tarafından ele alınıyor } } @@ -199,34 +200,27 @@ const IntranetDashboard: React.FC = () => { if (!grantedPolicies) return const hasSavedOrder = dashboardColumns.some((column) => widgetOrder[column].length > 0) - if (hasSavedOrder) { - try { - const parsed = widgetOrder - const order: Record = { - left: [...new Set((parsed.left || []) as string[])], - center: [...new Set((parsed.center || []) as string[])], - right: [...new Set((parsed.right || []) as string[])], - } - - const allAssigned = new Set([...order.left, ...order.center, ...order.right]) - dashboardWidgets.forEach((w) => { - if (!allAssigned.has(w.id) && checkPermission(w.permission)) { - order[w.column as keyof typeof order].push(w.id) - } - }) - - setWidgetOrder(order) - } catch { - initializeDefaultOrder() - } - } else { + if (!hasSavedOrder) { initializeDefaultOrder() + return } - }, [grantedPolicies]) - const saveWidgetOrder = (newOrder: Record) => { - setWidgetOrder(newOrder) - } + // Kayıtlı sıralamayı tekilleştir ve henüz yerleştirilmemiş widget'ları ekle + const order: Record = { + left: [...new Set(widgetOrder.left || [])], + center: [...new Set(widgetOrder.center || [])], + right: [...new Set(widgetOrder.right || [])], + } + + const allAssigned = new Set([...order.left, ...order.center, ...order.right]) + dashboardWidgets.forEach((widget) => { + if (!allAssigned.has(widget.id) && checkPermission(widget.permission)) { + order[widget.column].push(widget.id) + } + }) + + setWidgetOrder(order) + }, [grantedPolicies]) const setWidgetVisibility = (widgetId: string, visible: boolean) => { const next = visible @@ -235,10 +229,6 @@ const IntranetDashboard: React.FC = () => { setHiddenWidgetIds(next) } - const selectLayout = (layoutId: DashboardLayoutId) => { - setDashboardLayout(layoutId) - } - const activeLayout = layoutPresets.find((layout) => layout.id === selectedLayout) || layoutPresets[0] const hiddenWidgets = dashboardWidgets.filter( @@ -295,7 +285,7 @@ const IntranetDashboard: React.FC = () => { newOrder[targetColumn].push(widgetId) } - saveWidgetOrder(newOrder) + setWidgetOrder(newOrder) setDragState({ draggedId: null, targetColumn: null, targetIndex: null }) } @@ -532,7 +522,7 @@ const IntranetDashboard: React.FC = () => { type="button" variant="plain" shape="none" - onClick={() => selectLayout(layout.id)} + onClick={() => setDashboardLayout(layout.id)} className={`!h-8 !rounded-md !px-2 ${selectedLayout === layout.id ? '!bg-blue-600 text-white hover:!bg-blue-600' : '!bg-gray-100 text-gray-600 hover:!bg-gray-200 dark:!bg-gray-700 dark:text-gray-200'}`} title={`${translate(layout.labelKey)} (${layout.columns.join(' / ')})`} aria-label={`${translate(layout.labelKey)} (${layout.columns.join(' / ')})`} diff --git a/ui/src/views/intranet/SocialWall/CreatePost.tsx b/ui/src/views/intranet/SocialWall/CreatePost.tsx index c7d61632..37224f2f 100644 --- a/ui/src/views/intranet/SocialWall/CreatePost.tsx +++ b/ui/src/views/intranet/SocialWall/CreatePost.tsx @@ -1,20 +1,21 @@ -import React, { useState, useRef } from 'react' +import React, { useEffect, useMemo, useRef, useState } from 'react' import { motion, AnimatePresence } from 'framer-motion' import classNames from 'classnames' import type { EmojiClickData, Theme } from 'emoji-picker-react' -import { FaChartBar, FaSmile, FaTimes, FaImages, FaMapMarkerAlt } from 'react-icons/fa' +import { FaChartBar, FaSmile, FaTimes, FaImages } from 'react-icons/fa' import MediaManager from './MediaManager' -import LocationPicker from './LocationPicker' import { SocialMediaDto } from '@/proxy/intranet/models' import { useLocalization } from '@/utils/hooks/useLocalization' import { useStoreState } from '@/store/store' import { Avatar, Button } from '@/components/ui' import { AVATAR_URL } from '@/constants/app.constant' +const MAX_POLL_OPTIONS = 6 +const MIN_POLL_OPTIONS = 2 + interface CreatePostProps { onCreatePost: (post: { content: string - location?: string media?: { type: 'mixed' | 'poll' mediaItems?: SocialMediaDto[] @@ -28,103 +29,97 @@ interface CreatePostProps { const EmojiPicker = React.lazy(() => import('emoji-picker-react')) +/** Bir sonraki tıklama hedefini temizleyen link/plain buton stilleri */ +const LINK_BUTTON_CLASS = + '!h-auto !rounded-none !px-0 !py-0 text-sm font-medium hover:!bg-transparent active:!bg-transparent focus:!bg-transparent' +const TOOLBAR_BUTTON_CLASS = '!h-9 !w-9 !px-0 transition-colors' +const TOOLBAR_ACTIVE_CLASS = '!bg-blue-100 text-blue-600 dark:!bg-blue-900 dark:text-blue-400' +const TOOLBAR_IDLE_CLASS = + 'text-gray-600 hover:!bg-gray-100 dark:text-gray-400 dark:hover:!bg-gray-700' + const CreatePost: React.FC = ({ onCreatePost }) => { const { translate } = useLocalization() const [content, setContent] = useState('') const [mediaType, setMediaType] = useState<'media' | 'poll' | null>(null) const [mediaItems, setMediaItems] = useState([]) - const [location, setLocation] = useState(null) const [pollQuestion, setPollQuestion] = useState('') const [pollOptions, setPollOptions] = useState(['', '']) const [isExpanded, setIsExpanded] = useState(false) const [showEmojiPicker, setShowEmojiPicker] = useState(false) const [showMediaManager, setShowMediaManager] = useState(false) - const [showLocationPicker, setShowLocationPicker] = useState(false) const textareaRef = useRef(null) - const emojiPickerRef = useRef(null) + const emojiContainerRef = useRef(null) const { user, tenant } = useStoreState((state) => state.auth) const theme = useStoreState((state) => state.theme) - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault() + const filledPollOptions = useMemo( + () => pollOptions.map((option) => option.trim()).filter(Boolean), + [pollOptions], + ) + const hasMedia = mediaType === 'media' && mediaItems.length > 0 + const hasPoll = + mediaType === 'poll' && !!pollQuestion.trim() && filledPollOptions.length >= MIN_POLL_OPTIONS + const canSubmit = !!content.trim() || hasMedia || hasPoll - if (!content.trim() && mediaItems.length === 0 && !mediaType) return - - let media = undefined - - if (mediaType === 'media' && mediaItems.length > 0) { - media = { - type: 'mixed' as const, - mediaItems, - } - } else if ( - mediaType === 'poll' && - pollQuestion && - pollOptions.filter((o) => o.trim()).length >= 2 - ) { - media = { - type: 'poll' as const, - poll: { - question: pollQuestion, - options: pollOptions.filter((o) => o.trim()).map((text) => ({ text })), - }, - } - } - - onCreatePost({ - content, - media, - location: location || undefined, - }) - - // Reset form + const resetForm = () => { setContent('') setMediaType(null) setMediaItems([]) - setLocation(null) setPollQuestion('') setPollOptions(['', '']) setIsExpanded(false) setShowEmojiPicker(false) } + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + if (!canSubmit) return + + let media: Parameters[0]['media'] + + if (hasMedia) { + media = { type: 'mixed', mediaItems } + } else if (hasPoll) { + media = { + type: 'poll', + poll: { + question: pollQuestion.trim(), + options: filledPollOptions.map((text) => ({ text })), + }, + } + } + + onCreatePost({ content, media }) + resetForm() + } + const handleEmojiClick = (emojiData: EmojiClickData) => { - const emoji = emojiData.emoji const textarea = textareaRef.current if (!textarea) return + const { emoji } = emojiData const start = textarea.selectionStart const end = textarea.selectionEnd - const text = content - const before = text.substring(0, start) - const after = text.substring(end) - setContent(before + emoji + after) + setContent((prev) => prev.substring(0, start) + emoji + prev.substring(end)) - // Set cursor position after emoji - setTimeout(() => { + // Emoji eklendikten sonra imleci emojinin sonuna taşı + requestAnimationFrame(() => { textarea.selectionStart = textarea.selectionEnd = start + emoji.length textarea.focus() - }, 0) + }) } - const addPollOption = () => { - if (pollOptions.length < 6) { - setPollOptions([...pollOptions, '']) - } - } + const addPollOption = () => + setPollOptions((prev) => (prev.length < MAX_POLL_OPTIONS ? [...prev, ''] : prev)) - const removePollOption = (index: number) => { - if (pollOptions.length > 2) { - setPollOptions(pollOptions.filter((_, i) => i !== index)) - } - } + const removePollOption = (index: number) => + setPollOptions((prev) => + prev.length > MIN_POLL_OPTIONS ? prev.filter((_, i) => i !== index) : prev, + ) - const updatePollOption = (index: number, value: string) => { - const newOptions = [...pollOptions] - newOptions[index] = value - setPollOptions(newOptions) - } + const updatePollOption = (index: number, value: string) => + setPollOptions((prev) => prev.map((option, i) => (i === index ? value : option))) const clearMedia = () => { setMediaType(null) @@ -135,24 +130,21 @@ const CreatePost: React.FC = ({ onCreatePost }) => { const removeMediaItem = (id: string | undefined) => { if (!id) return - setMediaItems(mediaItems.filter((m) => m.id !== id)) + setMediaItems((prev) => prev.filter((m) => m.id !== id)) } - // Close emoji picker when clicking outside - React.useEffect(() => { + // Emoji seçici dışına tıklanınca kapat (tetikleyici buton da kapsayıcının içinde) + useEffect(() => { + if (!showEmojiPicker) return + const handleClickOutside = (event: MouseEvent) => { - if (emojiPickerRef.current && !emojiPickerRef.current.contains(event.target as Node)) { + if (!emojiContainerRef.current?.contains(event.target as Node)) { setShowEmojiPicker(false) } } - if (showEmojiPicker) { - document.addEventListener('mousedown', handleClickOutside) - } - - return () => { - document.removeEventListener('mousedown', handleClickOutside) - } + document.addEventListener('mousedown', handleClickOutside) + return () => document.removeEventListener('mousedown', handleClickOutside) }, [showEmojiPicker]) return ( @@ -179,7 +171,7 @@ const CreatePost: React.FC = ({ onCreatePost }) => { {/* Media Preview */} - {mediaType === 'media' && mediaItems.length > 0 && ( + {hasMedia && ( = ({ onCreatePost }) => { - -
-
- -
-

- {JSON.parse(location).name} -

-

- {JSON.parse(location).address} -

-
-
-
-
- )} - {mediaType === 'poll' && ( = ({ onCreatePost }) => { @@ -382,23 +336,19 @@ const CreatePost: React.FC = ({ onCreatePost }) => { @@ -508,13 +438,6 @@ const CreatePost: React.FC = ({ onCreatePost }) => { /> )}
- - {/* Location Picker Modal */} - - {showLocationPicker && ( - setShowLocationPicker(false)} /> - )} - ) } diff --git a/ui/src/views/intranet/SocialWall/LocationMap.tsx b/ui/src/views/intranet/SocialWall/LocationMap.tsx deleted file mode 100644 index 3a4c044e..00000000 --- a/ui/src/views/intranet/SocialWall/LocationMap.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import React from 'react' -import { useLocalization } from '@/utils/hooks/useLocalization' -import { FaExternalLinkAlt, FaMapMarkerAlt } from 'react-icons/fa' -import Button from '@/components/ui/Button' - -interface LocationData { - id: string - name: string - address: string - lat: number - lng: number - placeId?: string -} - -interface LocationMapProps { - location: string // JSON string - className?: string - showDirections?: boolean -} - -const LocationMap: React.FC = ({ - location, - className = '', - showDirections = true, -}) => { - const locationData: LocationData = JSON.parse(location) - - const handleOpenGoogleMaps = () => { - const url = `https://www.google.com/maps/search/?api=1&query=${locationData.lat},${locationData.lng}&query_place_id=${locationData.placeId || ''}` - window.open(url, '_blank') - } - - // Google Maps Static API URL (gerçek uygulamada API key eklenecek) - const getMapImageUrl = () => { - const { lat, lng } = locationData - const zoom = 15 - const size = '600x300' - const marker = `color:red|${lat},${lng}` - - // Production'da gerçek API key kullanılacak - // const apiKey = 'YOUR_GOOGLE_MAPS_API_KEY' - // return `https://maps.googleapis.com/maps/api/staticmap?center=${lat},${lng}&zoom=${zoom}&size=${size}&markers=${marker}&key=${apiKey}` - - // Demo için OpenStreetMap kullanıyoruz - return `https://www.openstreetmap.org/export/embed.html?bbox=${lng - 0.01},${lat - 0.01},${lng + 0.01},${lat + 0.01}&layer=mapnik&marker=${lat},${lng}` - } - - const { translate } = useLocalization() - return ( -
- {/* Map Container */} -
- {/* OpenStreetMap iframe for demo */} -