diff --git a/api/src/Sozsoft.Platform.Application/Messenger/MessengerAppService.cs b/api/src/Sozsoft.Platform.Application/Messenger/MessengerAppService.cs index 79ef625..8c2ed50 100644 --- a/api/src/Sozsoft.Platform.Application/Messenger/MessengerAppService.cs +++ b/api/src/Sozsoft.Platform.Application/Messenger/MessengerAppService.cs @@ -11,12 +11,14 @@ using Sozsoft.Platform.BlobStoring; using Sozsoft.Platform.Entities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Configuration; using Volo.Abp; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; using Volo.Abp.Domain.Repositories; using Volo.Abp.Identity; +using Volo.Abp.Uow; namespace Sozsoft.Platform.Messenger; @@ -29,6 +31,7 @@ public class MessengerAppService : ApplicationService private readonly IIdentitySessionRepository _identitySessionRepository; private readonly BlobManager _blobManager; private readonly IConfiguration _configuration; + private readonly IHubContext _messengerHubContext; public MessengerAppService( IRepository userRepository, @@ -36,7 +39,8 @@ public class MessengerAppService : ApplicationService IRepository messageRepository, IIdentitySessionRepository identitySessionRepository, BlobManager blobManager, - IConfiguration configuration) + IConfiguration configuration, + IHubContext messengerHubContext) { _userRepository = userRepository; _conversationRepository = conversationRepository; @@ -44,6 +48,7 @@ public class MessengerAppService : ApplicationService _identitySessionRepository = identitySessionRepository; _blobManager = blobManager; _configuration = configuration; + _messengerHubContext = messengerHubContext; } public async Task> GetContactsAsync(string? filter = null) @@ -133,6 +138,7 @@ public class MessengerAppService : ApplicationService return MapConversation(conversation); } + [UnitOfWork] public async Task CreateConversationAsync(MessengerConversationCreateUpdateDto input) { var currentUserId = GetCurrentUserId(); @@ -159,6 +165,7 @@ public class MessengerAppService : ApplicationService return MapConversation(conversation); } + [UnitOfWork] public async Task UpdateConversationAsync(Guid id, MessengerConversationCreateUpdateDto input) { var conversation = await _conversationRepository.GetAsync(id); @@ -178,6 +185,7 @@ public class MessengerAppService : ApplicationService return MapConversation(conversation); } + [UnitOfWork] public async Task DeleteConversationAsync(Guid id) { var conversation = await _conversationRepository.GetAsync(id); @@ -220,6 +228,8 @@ public class MessengerAppService : ApplicationService .ToList(); } + [UnitOfWork] + [HttpPost("api/app/messenger/send-message")] public async Task SendMessageAsync(MessengerSendMessageDto input) { var senderId = GetCurrentUserId(); @@ -271,7 +281,7 @@ public class MessengerAppService : ApplicationService IsGroup = participantIds.Count > 2 }; - await _conversationRepository.InsertAsync(conversation, autoSave: true); + await _conversationRepository.InsertAsync(conversation, autoSave: false); } var senderName = $"{CurrentUser.Name} {CurrentUser.SurName}".Trim() ?? CurrentUser.UserName ?? "Kullanici"; @@ -289,17 +299,23 @@ public class MessengerAppService : ApplicationService SentAt = Clock.Now }; - await _messageRepository.InsertAsync(message, autoSave: true); + await _messageRepository.InsertAsync(message, autoSave: false); conversation.LastSenderId = senderId; conversation.LastMessagePreview = GetMessagePreview(trimmedText, input.Attachments.Count); conversation.LastMessageTime = message.SentAt; conversation.MessageCount += 1; - await _conversationRepository.UpdateAsync(conversation, autoSave: true); + await _conversationRepository.UpdateAsync(conversation, autoSave: false); + await CurrentUnitOfWork!.SaveChangesAsync(); - return MapMessage(message); + var messageDto = MapMessage(message); + await PublishMessageReceivedAsync(messageDto); + + return messageDto; } + [UnitOfWork] + [HttpDelete("api/app/messenger/messages/{id}")] public async Task DeleteMessageAsync(Guid id) { var currentUserId = GetCurrentUserId(); @@ -335,13 +351,17 @@ public class MessengerAppService : ApplicationService conversation.LastMessageTime = lastMessage?.SentAt; await _conversationRepository.UpdateAsync(conversation, autoSave: true); - return new MessengerMessageDeletedDto + var deletedMessage = new MessengerMessageDeletedDto { MessageId = message.Id, ConversationId = message.ConversationId, SenderId = message.SenderId, RecipientIds = recipientIds }; + + await PublishMessageDeletedAsync(deletedMessage); + + return deletedMessage; } [HttpPost("api/app/messenger/upload-attachment")] @@ -523,4 +543,26 @@ public class MessengerAppService : ApplicationService return attachmentCount > 0 ? $"{attachmentCount} dosya" : string.Empty; } + + private async Task PublishMessageReceivedAsync(MessengerMessageDto message) + { + var targetGroups = message.RecipientIds + .Append(message.SenderId) + .Distinct() + .Select(userId => MessengerHub.UserGroupName(CurrentTenant.Id, userId)) + .ToList(); + + await _messengerHubContext.Clients.Groups(targetGroups).SendAsync("MessengerMessageReceived", message); + } + + private async Task PublishMessageDeletedAsync(MessengerMessageDeletedDto message) + { + var targetGroups = message.RecipientIds + .Append(message.SenderId) + .Distinct() + .Select(userId => MessengerHub.UserGroupName(CurrentTenant.Id, userId)) + .ToList(); + + await _messengerHubContext.Clients.Groups(targetGroups).SendAsync("MessengerMessageDeleted", message); + } } diff --git a/api/src/Sozsoft.Platform.Application/Messenger/MessengerHub.cs b/api/src/Sozsoft.Platform.Application/Messenger/MessengerHub.cs index caf9cd2..5a6b2b8 100644 --- a/api/src/Sozsoft.Platform.Application/Messenger/MessengerHub.cs +++ b/api/src/Sozsoft.Platform.Application/Messenger/MessengerHub.cs @@ -51,33 +51,34 @@ public class MessengerHub : Hub public async Task SendMessage(MessengerSendMessageDto input) { - var message = await _messengerAppService.SendMessageAsync(input); - - var targetGroups = message.RecipientIds - .Append(message.SenderId) - .Distinct() - .Select(UserGroupName) - .ToList(); - - await Clients.Groups(targetGroups).SendAsync("MessengerMessageReceived", message); + try + { + await _messengerAppService.SendMessageAsync(input); + } + catch (OperationCanceledException) when (Context.ConnectionAborted.IsCancellationRequested) + { + return; + } } public async Task DeleteMessage(Guid messageId) { - var deletedMessage = await _messengerAppService.DeleteMessageAsync(messageId); - - var targetGroups = deletedMessage.RecipientIds - .Append(deletedMessage.SenderId) - .Distinct() - .Select(UserGroupName) - .ToList(); - - await Clients.Groups(targetGroups).SendAsync("MessengerMessageDeleted", deletedMessage); + try + { + await _messengerAppService.DeleteMessageAsync(messageId); + } + catch (OperationCanceledException) when (Context.ConnectionAborted.IsCancellationRequested) + { + return; + } } private string TenantKey => _currentTenant.Id?.ToString("N") ?? "host"; private string TenantGroupName() => $"messenger:tenant:{TenantKey}"; - private string UserGroupName(Guid userId) => $"messenger:user:{TenantKey}:{userId:N}"; + public static string UserGroupName(Guid? tenantId, Guid userId) => + $"messenger:user:{tenantId?.ToString("N") ?? "host"}:{userId:N}"; + + private string UserGroupName(Guid userId) => UserGroupName(_currentTenant.Id, userId); } diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs index f7fff24..e523a29 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs @@ -1421,8 +1421,6 @@ public class PlatformDbContext : b.Property(x => x.LastMessagePreview).HasMaxLength(512); b.Property(x => x.MessageCount).HasDefaultValue(0); - b.HasIndex(x => new { x.TenantId, x.ParticipantKey }).IsUnique().HasFilter("[IsDeleted] = 0"); - b.HasMany(x => x.Messages) .WithOne(x => x.Conversation) .HasForeignKey(x => x.ConversationId) diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260624175615_Initial.Designer.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260625183130_Initial.Designer.cs similarity index 99% rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260624175615_Initial.Designer.cs rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260625183130_Initial.Designer.cs index ed73e32..54da8c8 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260624175615_Initial.Designer.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260625183130_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace Sozsoft.Platform.Migrations { [DbContext(typeof(PlatformDbContext))] - [Migration("20260624175615_Initial")] + [Migration("20260625183130_Initial")] partial class Initial { /// @@ -3904,10 +3904,6 @@ namespace Sozsoft.Platform.Migrations b.HasKey("Id"); - b.HasIndex("TenantId", "ParticipantKey") - .IsUnique() - .HasFilter("[IsDeleted] = 0"); - b.ToTable("Adm_T_MessengerConversation", (string)null); }); diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260624175615_Initial.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260625183130_Initial.cs similarity index 99% rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260624175615_Initial.cs rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260625183130_Initial.cs index 25c26d3..c5e2970 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260624175615_Initial.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260625183130_Initial.cs @@ -3730,13 +3730,6 @@ namespace Sozsoft.Platform.Migrations unique: true, filter: "[IsDeleted] = 0"); - migrationBuilder.CreateIndex( - name: "IX_Adm_T_MessengerConversation_TenantId_ParticipantKey", - table: "Adm_T_MessengerConversation", - columns: new[] { "TenantId", "ParticipantKey" }, - unique: true, - filter: "[IsDeleted] = 0"); - migrationBuilder.CreateIndex( name: "IX_Adm_T_MessengerConversationMessage_ConversationId", table: "Adm_T_MessengerConversationMessage", diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs index 089d070..7f7d7b6 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs @@ -3901,10 +3901,6 @@ namespace Sozsoft.Platform.Migrations b.HasKey("Id"); - b.HasIndex("TenantId", "ParticipantKey") - .IsUnique() - .HasFilter("[IsDeleted] = 0"); - b.ToTable("Adm_T_MessengerConversation", (string)null); }); diff --git a/ui/src/components/codeLayout/ComponentCodeEditor.tsx b/ui/src/components/codeLayout/ComponentCodeEditor.tsx index a4449a1..c659223 100644 --- a/ui/src/components/codeLayout/ComponentCodeEditor.tsx +++ b/ui/src/components/codeLayout/ComponentCodeEditor.tsx @@ -2,8 +2,8 @@ import React, { useState, useEffect, useRef } from 'react' import Editor from '@monaco-editor/react' import { ComponentDefinition } from '../../proxy/developerKit/componentInfo' import { generateSingleComponentJSX, generateUniqueId } from '@/utils/codeParser' -import { FaCheck, FaCode, FaSpinner, FaMousePointer, FaSave, FaCog, FaTimes } from 'react-icons/fa' -import { toast } from '../ui' +import { FaCheck, FaCode, FaMousePointer, FaSave, FaCog, FaTimes } from 'react-icons/fa' +import { Button, toast } from '../ui' import Notification from '../ui/Notification/Notification' interface ComponentCodeEditorProps { @@ -560,55 +560,57 @@ export const ComponentCodeEditor: React.FC = ({ {/* Header */}
- + - + - + - +
- +
@@ -656,14 +658,15 @@ export const ComponentCodeEditor: React.FC = ({
- +
diff --git a/ui/src/components/codeLayout/PanelManager.tsx b/ui/src/components/codeLayout/PanelManager.tsx index 72d9a4f..2f7b311 100644 --- a/ui/src/components/codeLayout/PanelManager.tsx +++ b/ui/src/components/codeLayout/PanelManager.tsx @@ -6,6 +6,7 @@ import { FaEye, FaEyeSlash } from 'react-icons/fa'; +import { Button } from "../ui"; import { PanelState } from "./data/componentDefinitions"; interface PanelManagerProps { @@ -37,13 +38,13 @@ export const PanelManager: React.FC = ({

Panel Manager

- + />

Customize Workspace

@@ -55,13 +56,20 @@ export const PanelManager: React.FC = ({ {label}
- + /> ))} diff --git a/ui/src/components/codeLayout/PropertyPanel.tsx b/ui/src/components/codeLayout/PropertyPanel.tsx index 811f693..7840fce 100644 --- a/ui/src/components/codeLayout/PropertyPanel.tsx +++ b/ui/src/components/codeLayout/PropertyPanel.tsx @@ -3,6 +3,7 @@ import TailwindModal from "./TailwindModal"; import { ComponentInfo, HookInfo, PropertyInfo } from "../../proxy/developerKit/componentInfo"; import { getComponentDefinition } from "./data/componentDefinitions"; import { Button } from "../ui"; +import { FaCheck, FaCode, FaTimes, FaTrash } from "react-icons/fa"; interface PropertyPanelProps { selectedComponent: ComponentInfo | null; @@ -284,13 +285,16 @@ const PropertyPanel: React.FC = ({ placeholder={`Enter ${property.name}`} /> {isTailwindProperty && ( - + )} {isColorProperty && ( = ({ placeholder={`Enter ${property.name}`} /> {isTailwindProperty && ( - + )} {isColorProperty && ( = ({ )} {/* Tabs */}
- - +
{/* Sil Butonu */} diff --git a/ui/src/components/codeLayout/TailwindModal.tsx b/ui/src/components/codeLayout/TailwindModal.tsx index 0adacb9..aebcb7a 100644 --- a/ui/src/components/codeLayout/TailwindModal.tsx +++ b/ui/src/components/codeLayout/TailwindModal.tsx @@ -1,6 +1,7 @@ import { useState, useEffect } from 'react'; import { searchTailwindClasses, TAILWIND_CLASSES } from './data/tailwindClasses'; import { Button } from '../ui'; +import { FaTimes } from 'react-icons/fa'; interface TailwindModalProps { isOpen: boolean; @@ -53,12 +54,13 @@ const TailwindModal: React.FC = ({ {/* Header */}

Tailwind CSS Classes

- + icon={} + variant="plain" + size="xs" + title="Close" + />
{/* Search and Filter */} @@ -108,9 +110,12 @@ const TailwindModal: React.FC = ({ className="group relative" > @@ -149,4 +155,4 @@ const TailwindModal: React.FC = ({ ); }; -export default TailwindModal; \ No newline at end of file +export default TailwindModal; diff --git a/ui/src/components/importManager/FileUploadArea.tsx b/ui/src/components/importManager/FileUploadArea.tsx index dc56124..1c75670 100644 --- a/ui/src/components/importManager/FileUploadArea.tsx +++ b/ui/src/components/importManager/FileUploadArea.tsx @@ -1,6 +1,7 @@ import { useLocalization } from '@/utils/hooks/useLocalization' import React, { useCallback, useState } from 'react' import { FaUpload, FaFile, FaTimes, FaRegCircle } from 'react-icons/fa' +import { Button } from '@/components/ui' interface FileUploadAreaProps { onFileUpload: (file: File) => void @@ -149,12 +150,13 @@ export const FileUploadArea: React.FC = ({ - + icon={} + variant="plain" + size="xs" + title={translate('::Clear')} + /> )} @@ -167,23 +169,19 @@ export const FileUploadArea: React.FC = ({ )} {selectedFile && !error && ( - + {loading + ? translate('::App.Uploading') + : translate('::App.Listforms.ImportManager.UploadFile')} + )} ) diff --git a/ui/src/components/importManager/ImportDashboard.tsx b/ui/src/components/importManager/ImportDashboard.tsx index ad06f9d..63e50e5 100644 --- a/ui/src/components/importManager/ImportDashboard.tsx +++ b/ui/src/components/importManager/ImportDashboard.tsx @@ -23,6 +23,7 @@ import { ImportService } from '@/services/import.service' import { GridDto } from '@/proxy/form/models' import { useLocalization } from '@/utils/hooks/useLocalization' import { useDialogContext } from '@/components/ui/Dialog/Dialog' +import { Button } from '@/components/ui' interface ImportDashboardProps { gridDto: GridDto @@ -333,20 +334,25 @@ export const ImportDashboard: React.FC = ({ gridDto }) => {/* Navigation Tabs */}
{['import', 'preview', 'history'].map((tab) => ( - + ))}
@@ -368,27 +374,27 @@ export const ImportDashboard: React.FC = ({ gridDto }) => {/* Template Options */}
- + {translate('::App.Listforms.ImportManager.ExcelTemplate')} + - + {translate('::App.Listforms.ImportManager.CsvTemplate')} +
@@ -567,18 +573,15 @@ export const ImportDashboard: React.FC = ({ gridDto }) =>
- - - + />
@@ -730,20 +731,24 @@ export const ImportDashboard: React.FC = ({ gridDto }) => execute.status === 'failed') && execute.errorRows > 0 && (
- + {expandedErrors.has(execute.id) && (
diff --git a/ui/src/components/importManager/ImportPreview.tsx b/ui/src/components/importManager/ImportPreview.tsx index 93df1bc..832ad26 100644 --- a/ui/src/components/importManager/ImportPreview.tsx +++ b/ui/src/components/importManager/ImportPreview.tsx @@ -4,6 +4,7 @@ import { ListFormImportDto } from '@/proxy/imports/models' import { GridDto } from '@/proxy/form/models' import { ImportService } from '@/services/import.service' import { useLocalization } from '@/utils/hooks/useLocalization' +import { Button } from '@/components/ui' interface ImportPreviewProps { session: ListFormImportDto @@ -314,28 +315,27 @@ export const ImportPreview: React.FC = ({
- + - + {loading + ? translate('::App.Listforms.Status.Processing') + : translate('::App.Listforms.ImportManager.Button.ExecuteImport')} +
diff --git a/ui/src/components/layouts/PublicLayout.tsx b/ui/src/components/layouts/PublicLayout.tsx index 5162fb9..5e098c7 100644 --- a/ui/src/components/layouts/PublicLayout.tsx +++ b/ui/src/components/layouts/PublicLayout.tsx @@ -24,6 +24,7 @@ import Demo from '@/views/public/Demo' import { ScrollContext } from '@/contexts/ScrollContext' import useDarkMode from '@/utils/hooks/useDarkmode' import { THEME_ENUM } from '@/constants/theme.constant' +import { Button } from '../ui' interface NavLink { resourceKey?: string @@ -108,13 +109,15 @@ const PublicLayout = () => { const isLoginLink = (resourceKey?: string) => resourceKey === 'Public.nav.giris' const demoButton = (className = '', onClick?: () => void) => ( - + ) const themeToggle = ( -
- + /> - + />
) @@ -207,9 +216,11 @@ const PublicLayout = () => { /> ) : ( - + ) })} {/* Right side: Language + Login */} -
-
- {demoButton('h-9 px-3 rounded-lg')} -
+
+ {demoButton('rounded-lg')} +
+
+
{themeToggle} -
+
{navLinks .filter((l) => isLoginLink(l.resourceKey)) .map((link) => @@ -242,7 +254,7 @@ const PublicLayout = () => { {link.name} @@ -251,14 +263,21 @@ const PublicLayout = () => {
{/* Mobile Menu Button */} - + icon={ + isOpen ? ( + + ) : ( + + ) + } + variant="plain" + size="xs" + />
{/* Mobile Navigation */} @@ -309,26 +328,28 @@ const PublicLayout = () => { {link.name} ) : ( - + ) })}
- {demoButton('h-10 px-3 rounded-lg', toggleMenu)} + {demoButton('rounded-lg', toggleMenu)}
diff --git a/ui/src/components/orders/BillingControls.tsx b/ui/src/components/orders/BillingControls.tsx index 298b09a..ef30a2d 100644 --- a/ui/src/components/orders/BillingControls.tsx +++ b/ui/src/components/orders/BillingControls.tsx @@ -4,6 +4,7 @@ import { BillingCycle } from '@/proxy/order/models' import { CartState } from '@/utils/cartUtils' import { useScroll } from '@/contexts/ScrollContext' import { useLocalization } from '@/utils/hooks/useLocalization' +import { Button } from '@/components/ui' interface BillingControlsProps { globalBillingCycle: BillingCycle @@ -48,38 +49,32 @@ export const BillingControls: React.FC = ({
- - +
@@ -93,20 +88,16 @@ export const BillingControls: React.FC = ({
- + /> -
+
{globalPeriod} {globalBillingCycle === 'monthly' @@ -115,26 +106,25 @@ export const BillingControls: React.FC = ({
- + />
- +
diff --git a/ui/src/components/orders/Cart.tsx b/ui/src/components/orders/Cart.tsx index b6acd5e..c0dc600 100644 --- a/ui/src/components/orders/Cart.tsx +++ b/ui/src/components/orders/Cart.tsx @@ -2,6 +2,7 @@ import React from 'react' import { FaTimes, FaMinus, FaPlus, FaShoppingBag, FaTrash } from 'react-icons/fa' import { BillingCycle, BasketItem } from '@/proxy/order/models' import { useLocalization } from '@/utils/hooks/useLocalization' +import { Button } from '@/components/ui' interface CartState { items: BasketItem[] @@ -59,20 +60,21 @@ export const Cart: React.FC = ({
{cartState.items.length > 0 && ( - + /> )} - + icon={} + variant="plain" + size="xs" + />
@@ -94,12 +96,14 @@ export const Cart: React.FC = ({

{translate('::' + item.product.name)}

- + icon={} + variant="plain" + color="red-600" + size="xs" + className="ml-2" + />
@@ -125,19 +129,19 @@ export const Cart: React.FC = ({ {item.product.isQuantityBased && (
- + icon={} + variant="default" + size="xs" + /> {item.quantity} - + icon={} + variant="default" + size="xs" + />
)}
@@ -162,12 +166,15 @@ export const Cart: React.FC = ({ - + )} diff --git a/ui/src/components/orders/OrderSuccess.tsx b/ui/src/components/orders/OrderSuccess.tsx index eeed79a..4c53cb5 100644 --- a/ui/src/components/orders/OrderSuccess.tsx +++ b/ui/src/components/orders/OrderSuccess.tsx @@ -3,6 +3,7 @@ import { FaCheckCircle, FaArrowLeft } from 'react-icons/fa' import { useNavigate } from 'react-router-dom' import { ROUTES_ENUM } from '@/routes/route.constant' import { useLocalization } from '@/utils/hooks/useLocalization' +import { Button } from '@/components/ui' interface OrderSuccessProps { orderId: string @@ -40,13 +41,14 @@ export const OrderSuccess: React.FC = ({ orderId, onBackToSho
- +
diff --git a/ui/src/components/orders/PaymentForm.tsx b/ui/src/components/orders/PaymentForm.tsx index 19425e5..43928a6 100644 --- a/ui/src/components/orders/PaymentForm.tsx +++ b/ui/src/components/orders/PaymentForm.tsx @@ -23,6 +23,7 @@ import { import { OrderService } from '@/services/order.service' import { CustomTenantDto } from '@/proxy/config/models' import { useLocalization } from '@/utils/hooks/useLocalization' +import { Button } from '@/components/ui' interface CartState { items: BasketItem[] @@ -443,23 +444,26 @@ export const PaymentForm: React.FC = ({ {/* Butonlar */}
- - +
diff --git a/ui/src/components/orders/ProductCard.tsx b/ui/src/components/orders/ProductCard.tsx index 71903e3..4e4e98b 100644 --- a/ui/src/components/orders/ProductCard.tsx +++ b/ui/src/components/orders/ProductCard.tsx @@ -3,6 +3,7 @@ import { FaPlus, FaMinus } from 'react-icons/fa' import { BillingCycle, Product, ProductDto } from '@/proxy/order/models' import { CartState } from '@/utils/cartUtils' import { useLocalization } from '@/utils/hooks/useLocalization' +import { Button } from '@/components/ui' interface ProductCardProps { product: ProductDto @@ -119,19 +120,19 @@ export const ProductCard: React.FC = ({ {translate('::Public.products.quantity')}
- - {quantity} - + icon={} + variant="default" + size="xs" + />
)} @@ -165,17 +166,17 @@ export const ProductCard: React.FC = ({ const isDisabled = isNonQuantityProduct && isInCart return ( - + ) })()} diff --git a/ui/src/components/orders/ProductCatalog.tsx b/ui/src/components/orders/ProductCatalog.tsx index 51db000..83d4335 100644 --- a/ui/src/components/orders/ProductCatalog.tsx +++ b/ui/src/components/orders/ProductCatalog.tsx @@ -6,6 +6,7 @@ import { BillingCycle, ProductDto } from '@/proxy/order/models' import { OrderService } from '@/services/order.service' import { useLocalization } from '@/utils/hooks/useLocalization' import { Loading } from '../shared' +import { Button } from '@/components/ui' interface ProductCatalogProps { searchQuery: string @@ -88,7 +89,7 @@ export const ProductCatalog: React.FC = ({ return (
@@ -652,16 +696,19 @@ const MessengerWidget = () => { className="inline-flex max-w-[220px] items-center gap-2 rounded-md bg-gray-100 px-2 py-1 text-xs dark:bg-gray-700" > {file.fileName} - + /> ))} @@ -746,18 +793,23 @@ const MessengerWidget = () => {
- +
diff --git a/ui/src/services/messenger.service.ts b/ui/src/services/messenger.service.ts index 87fcde4..96fb3c5 100644 --- a/ui/src/services/messenger.service.ts +++ b/ui/src/services/messenger.service.ts @@ -28,6 +28,13 @@ export interface MessengerGetMessagesInput { sorting?: string } +export interface MessengerSendMessageDto { + conversationId?: string + recipientIds: string[] + text?: string + attachments: MessengerAttachmentDto[] +} + export interface MessengerMessageDto { id: string tenantId?: string @@ -62,6 +69,19 @@ export const getMessengerMessages = (data: MessengerGetMessagesInput) => data, }) +export const sendMessengerMessage = (data: MessengerSendMessageDto) => + apiService.fetchData({ + method: 'POST', + url: '/api/app/messenger/send-message', + data, + }) + +export const deleteMessengerMessage = (messageId: string) => + apiService.fetchData({ + method: 'DELETE', + url: `/api/app/messenger/messages/${messageId}`, + }) + export const uploadMessengerAttachment = (file: File) => { const formData = new FormData() formData.append('file', file) diff --git a/ui/src/services/messenger.signalr.ts b/ui/src/services/messenger.signalr.ts index e5dd38e..2b51e8c 100644 --- a/ui/src/services/messenger.signalr.ts +++ b/ui/src/services/messenger.signalr.ts @@ -1,13 +1,10 @@ import { store } from '@/store/store' import * as signalR from '@microsoft/signalr' -import { MessengerAttachmentDto, MessengerMessageDeletedDto, MessengerMessageDto } from './messenger.service' - -export interface MessengerSendMessageDto { - conversationId?: string - recipientIds: string[] - text?: string - attachments: MessengerAttachmentDto[] -} +import { + MessengerMessageDeletedDto, + MessengerMessageDto, + MessengerSendMessageDto, +} from './messenger.service' type MessageHandler = (message: MessengerMessageDto) => void type MessageDeletedHandler = (message: MessengerMessageDeletedDto) => void diff --git a/ui/src/views/admin/hr/OrgChart.tsx b/ui/src/views/admin/hr/OrgChart.tsx index 47c6449..f02358e 100644 --- a/ui/src/views/admin/hr/OrgChart.tsx +++ b/ui/src/views/admin/hr/OrgChart.tsx @@ -22,7 +22,7 @@ import { useLocalization } from '@/utils/hooks/useLocalization' import { APP_NAME } from '@/constants/app.constant' import Loading from '@/components/shared/Loading' import { useCurrentMenuIcon } from '@/utils/hooks/useCurrentMenuIcon' -import { Avatar } from '@/components/ui' +import { Avatar, Button } from '@/components/ui' import { AVATAR_URL } from '@/constants/app.constant' import { useStoreState } from '@/store' import UserProfileCard from '@/views/intranet/SocialWall/UserProfileCard' @@ -32,20 +32,20 @@ type ViewMode = 'department' | 'jobPosition' // ---- JPG Export helpers ---- // Tailwind class → hex (keep in sync with LEVEL_COLORS below) const TW_HEX: Record = { - 'bg-blue-500': '#3b82f6', + 'bg-blue-500': '#3b82f6', 'bg-indigo-500': '#6366f1', - 'bg-cyan-500': '#06b6d4', - 'bg-teal-500': '#14b8a6', + 'bg-cyan-500': '#06b6d4', + 'bg-teal-500': '#14b8a6', 'bg-violet-500': '#8b5cf6', - 'bg-sky-500': '#0ea5e9', - 'bg-slate-500': '#64748b', - 'border-blue-200': '#bfdbfe', + 'bg-sky-500': '#0ea5e9', + 'bg-slate-500': '#64748b', + 'border-blue-200': '#bfdbfe', 'border-indigo-200': '#c7d2fe', - 'border-cyan-200': '#a5f3fc', - 'border-teal-200': '#99f6e4', + 'border-cyan-200': '#a5f3fc', + 'border-teal-200': '#99f6e4', 'border-violet-200': '#ddd6fe', - 'border-sky-200': '#bae6fd', - 'border-slate-200': '#e2e8f0', + 'border-sky-200': '#bae6fd', + 'border-slate-200': '#e2e8f0', } function loadImg(src: string): Promise { @@ -59,7 +59,14 @@ function loadImg(src: string): Promise { }) } -function drawRR(ctx: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, r: number) { +function drawRR( + ctx: CanvasRenderingContext2D, + x: number, + y: number, + w: number, + h: number, + r: number, +) { ctx.beginPath() ctx.moveTo(x + r, y) ctx.lineTo(x + w - r, y) @@ -73,7 +80,14 @@ function drawRR(ctx: CanvasRenderingContext2D, x: number, y: number, w: number, ctx.closePath() } -function drawTopRR(ctx: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, r: number) { +function drawTopRR( + ctx: CanvasRenderingContext2D, + x: number, + y: number, + w: number, + h: number, + r: number, +) { ctx.beginPath() ctx.moveTo(x + r, y) ctx.lineTo(x + w - r, y) @@ -233,7 +247,13 @@ async function captureAsJpeg(element: HTMLElement, filename: string, zoom: numbe } else { const nameSpan = row.querySelector('[data-user-name]') as HTMLElement | null const nm = nameSpan?.textContent?.trim() || '' - const initials = nm.split(' ').map((p) => p[0]).filter(Boolean).slice(0, 2).join('').toUpperCase() + const initials = nm + .split(' ') + .map((p) => p[0]) + .filter(Boolean) + .slice(0, 2) + .join('') + .toUpperCase() ctx.save() ctx.font = `bold 8px sans-serif` ctx.fillStyle = '#4f46e5' @@ -276,13 +296,13 @@ async function captureAsJpeg(element: HTMLElement, filename: string, zoom: numbe // ---- end JPG Export helpers ---- const LEVEL_COLORS = [ - { border: 'border-blue-200', header: 'bg-blue-500', badge: 'bg-blue-50 text-blue-600' }, - { border: 'border-indigo-200', header: 'bg-indigo-500', badge: 'bg-indigo-50 text-indigo-600' }, - { border: 'border-cyan-200', header: 'bg-cyan-500', badge: 'bg-cyan-50 text-cyan-700' }, - { border: 'border-teal-200', header: 'bg-teal-500', badge: 'bg-teal-50 text-teal-700' }, - { border: 'border-violet-200', header: 'bg-violet-500', badge: 'bg-violet-50 text-violet-600' }, - { border: 'border-sky-200', header: 'bg-sky-500', badge: 'bg-sky-50 text-sky-700' }, - { border: 'border-slate-200', header: 'bg-slate-500', badge: 'bg-slate-50 text-slate-600' }, + { border: 'border-blue-200', header: 'bg-blue-500', badge: 'bg-blue-50 text-blue-600' }, + { border: 'border-indigo-200', header: 'bg-indigo-500', badge: 'bg-indigo-50 text-indigo-600' }, + { border: 'border-cyan-200', header: 'bg-cyan-500', badge: 'bg-cyan-50 text-cyan-700' }, + { border: 'border-teal-200', header: 'bg-teal-500', badge: 'bg-teal-50 text-teal-700' }, + { border: 'border-violet-200', header: 'bg-violet-500', badge: 'bg-violet-50 text-violet-600' }, + { border: 'border-sky-200', header: 'bg-sky-500', badge: 'bg-sky-50 text-sky-700' }, + { border: 'border-slate-200', header: 'bg-slate-500', badge: 'bg-slate-50 text-slate-600' }, ] interface TreeNode extends OrgChartNodeDto { @@ -477,7 +497,11 @@ function OrgChartNode({ nodeRefs.current[node.id] = el }} className="flex flex-col items-center select-none" - style={applySelfTransform ? { transform: `translate(${position.x}px, ${position.y}px)` } : undefined} + style={ + applySelfTransform + ? { transform: `translate(${position.x}px, ${position.y}px)` } + : undefined + } > {/* Node card */}
{/* Header bar */} -
+
{mode === 'department' ? ( ) : ( )} - + {node.name} {hasChildren && ( - + variant="plain" + shape="circle" + icon={ + collapsed ? ( + + ) : ( + + ) + } + className="ml-1 !h-5 !w-5 flex-shrink-0 !px-0 text-white opacity-70 transition-opacity hover:opacity-100" + > )}
- - {/* Users section */} {showUsers && (
@@ -531,7 +562,9 @@ function OrgChartNode({ @@ -557,15 +590,15 @@ function OrgChartNode({ {/* Children */} {hasChildren && !collapsed && (
- {node.children.map((child, idx) => { - const childPosition = positions[child.id] ?? { x: 0, y: 0 } + {node.children.map((child, idx) => { + const childPosition = positions[child.id] ?? { x: 0, y: 0 } - return ( -
+ return ( +
{/* Recursive child */}
- ) - })} + ) + })}
)}
@@ -848,28 +881,38 @@ const OrgChart = () => {
- - + + + {translate('::App.Hr.JobPosition') || 'Pozisyon'} + +
@@ -880,46 +923,53 @@ const OrgChart = () => { onChange={(e) => setShowUsers(e.target.checked)} className="h-4 w-4 rounded border-slate-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500" /> - {translate('::App.Definitions.OrgChart.ShowUsers') || 'Kullanıcılar'} + + {translate('::App.Definitions.OrgChart.ShowUsers') || 'Kullanıcılar'} +
- + /> {Math.round(zoom * 100)}% - - + />
- +
diff --git a/ui/src/views/admin/listForm/edit/form-fields/FormFieldTabLookup.tsx b/ui/src/views/admin/listForm/edit/form-fields/FormFieldTabLookup.tsx index d2bda3a..96af325 100644 --- a/ui/src/views/admin/listForm/edit/form-fields/FormFieldTabLookup.tsx +++ b/ui/src/views/admin/listForm/edit/form-fields/FormFieldTabLookup.tsx @@ -68,13 +68,14 @@ function TablePickerModal({
{step === 'columns' && ( - + variant="plain" + shape="circle" + icon={} + className="!h-6 !w-6 !px-0 text-gray-400 transition-colors hover:!bg-transparent hover:text-indigo-500" + /> )} {step === 'table' @@ -82,13 +83,14 @@ function TablePickerModal({ : (pickerTable?.tableName ?? '')}
- + variant="plain" + shape="circle" + icon={} + className="!h-7 !w-7 !px-0 text-gray-400 hover:!bg-transparent hover:text-gray-600 dark:hover:text-gray-200" + />
{/* Step 1: Table list */} @@ -112,7 +114,7 @@ function TablePickerModal({ dbObjects.tables .filter((t) => t.tableName.toLowerCase().includes(tableSearch.toLowerCase())) .map((t) => ( - + )) )}
@@ -199,7 +203,7 @@ function TablePickerModal({ })}
)} - + )} @@ -310,14 +317,16 @@ function FormFieldTabLookup({ } + className="ml-2 !h-auto !items-center gap-1 !rounded border border-indigo-200 bg-indigo-50 !px-1.5 !py-0.5 text-[10px] font-medium text-indigo-600 transition-colors hover:!bg-indigo-100 dark:border-indigo-700 dark:bg-indigo-900/20 dark:text-indigo-400 dark:hover:!bg-indigo-800/40" > - {translate('::ListForms.Wizard.Step3.GenerateFromTable') || 'Tablodan Oluştur'} - + } invalid={errors.lookupDto?.lookupQuery && touched.lookupDto?.lookupQuery} errorMessage={errors.lookupDto?.lookupQuery} diff --git a/ui/src/views/admin/listForm/wizard/WizardStep1.tsx b/ui/src/views/admin/listForm/wizard/WizardStep1.tsx index fc0cc3a..b8a61b5 100644 --- a/ui/src/views/admin/listForm/wizard/WizardStep1.tsx +++ b/ui/src/views/admin/listForm/wizard/WizardStep1.tsx @@ -182,52 +182,60 @@ function TreeNode({ {isEditing ? ( <> - - + size="xs" + variant="plain" + shape="circle" + icon={} + className="!h-6 !w-6 !px-0 text-gray-400 hover:!bg-transparent hover:text-gray-600" + /> ) : ( - - + size="xs" + variant="plain" + shape="circle" + icon={} + className={`!h-6 !w-6 !px-0 hover:!bg-transparent ${isSelected ? 'text-indigo-200 hover:text-white' : 'text-gray-300 hover:text-red-500'}`} + /> )} @@ -343,7 +351,12 @@ function MenuTreeInline({ const handleDelete = async (node: MenuTreeNode & { id?: string }) => { if (!node.id) return - if (!window.confirm(`"${node.displayName}" ${translate('::ListForms.Wizard.Step1.DeleteMenuConfirm') || 'menüsünü silmek istediğinize emin misiniz?'}`)) return + if ( + !window.confirm( + `"${node.displayName}" ${translate('::ListForms.Wizard.Step1.DeleteMenuConfirm') || 'menüsünü silmek istediğinize emin misiniz?'}`, + ) + ) + return setSaving(true) try { await menuService.delete(node.id) @@ -383,7 +396,9 @@ function MenuTreeInline({ {isLoading ? (
Loading…
) : enrichedNodes.length === 0 ? ( -
{translate('::ListForms.Wizard.Step1.NoMenusAvailable') || 'No menus available'}
+
+ {translate('::ListForms.Wizard.Step1.NoMenusAvailable') || 'No menus available'} +
) : ( enrichedNodes.map((node) => ( - {translate('::ListForms.Wizard.Step1.WizardNameHint') || 'Used to generate ListForm Code and Menu Code'} + {translate('::ListForms.Wizard.Step1.WizardNameHint') || + 'Used to generate ListForm Code and Menu Code'} } > @@ -501,7 +517,7 @@ const WizardStep1 = ({ errorMessage={undefined} extra={
- + + {translate('::ListForms.Wizard.Add') || 'Ekle'} + + {values.menuParentCode && ( - + {translate('::ListForms.Wizard.ClearSelection') || 'Seçimi Kaldır'} + )}
} @@ -541,7 +566,11 @@ const WizardStep1 = ({ isLoading={isLoadingMenu} invalid={false} onReload={onReloadMenu} - initialExpanded={values.menuParentCode ? getAncestorCodes(rawMenuItems, values.menuParentCode) : undefined} + initialExpanded={ + values.menuParentCode + ? getAncestorCodes(rawMenuItems, values.menuParentCode) + : undefined + } /> )} @@ -565,7 +594,11 @@ const WizardStep1 = ({ invalid={!!(errors.menuCode && touched.menuCode)} errorMessage={errors.menuCode} asterisk={true} - extra={{translate('::ListForms.Wizard.Step1.MenuCodeHint') || 'Auto-derived, editable'}} + extra={ + + {translate('::ListForms.Wizard.Step1.MenuCodeHint') || 'Auto-derived, editable'} + + } >
@@ -622,7 +657,9 @@ const WizardStep1 = ({ type="text" autoComplete="off" name="languageTextMenuTr" - placeholder={translate('::ListForms.Wizard.Step1.DisplayNameTurkish') || 'Turkish Menu Text'} + placeholder={ + translate('::ListForms.Wizard.Step1.DisplayNameTurkish') || 'Turkish Menu Text' + } component={Input} /> diff --git a/ui/src/views/admin/listForm/wizard/WizardStep3.tsx b/ui/src/views/admin/listForm/wizard/WizardStep3.tsx index 44c9a93..3d22c18 100644 --- a/ui/src/views/admin/listForm/wizard/WizardStep3.tsx +++ b/ui/src/views/admin/listForm/wizard/WizardStep3.tsx @@ -238,26 +238,28 @@ function SortableItem({ > {/* Top row: drag handle + field name + remove */}
- + /> {item.dataField} - + />
{/* Language Key + Turkish Caption + English Caption */} @@ -362,14 +364,16 @@ function SortableItem({ {translate('::ListForms.Wizard.Step3.LookupLookupQuery')} - +