This commit is contained in:
mmrbnjd
2025-08-08 18:10:25 +03:30
parent 0736632a59
commit f6013e482b
5 changed files with 242 additions and 148 deletions

View File

@@ -4,6 +4,7 @@ using Hushian.Application.Contracts.Persistence;
using Hushian.Application.Models; using Hushian.Application.Models;
using Hushian.Domain.Entites; using Hushian.Domain.Entites;
using Hushian.WebApi; using Hushian.WebApi;
using Microsoft.AspNetCore.Routing.Constraints;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System; using System;
@@ -77,7 +78,7 @@ namespace Hushian.Application.Services
.Where(w => w.CompanyID == CompanyID && w.Status == status) .Where(w => w.CompanyID == CompanyID && w.Status == status)
.Select(s => new ChatItemDto() .Select(s => new ChatItemDto()
{ {
ID = s.ID, ID = s.ID,
ExperID = s.ConversationResponses.OrderBy(o => o.ID).Last(l => l.ExperID.HasValue).ExperID, ExperID = s.ConversationResponses.OrderBy(o => o.ID).Last(l => l.ExperID.HasValue).ExperID,
ExperFullName = s.ConversationResponses.OrderBy(o => o.ID).Last(l => l.ExperID.HasValue).Exper.FullName, ExperFullName = s.ConversationResponses.OrderBy(o => o.ID).Last(l => l.ExperID.HasValue).Exper.FullName,
GroupID = s.GroupID, GroupID = s.GroupID,
@@ -117,7 +118,7 @@ namespace Hushian.Application.Services
.Include(inc => inc.Group) .Include(inc => inc.Group)
.Include(inc => inc.ConversationResponses).ThenInclude(tinc => tinc.Exper) .Include(inc => inc.ConversationResponses).ThenInclude(tinc => tinc.Exper)
.Where(w => w.Status == ConversationStatus.Recorded && w.CompanyID == CompanyID); .Where(w => w.Status == ConversationStatus.Recorded && w.CompanyID == CompanyID);
if (groupallow != null) request = request.Where(w=>!w.GroupID.HasValue || groupallow.Contains(w.GroupID.Value)); if (groupallow != null) request = request.Where(w => !w.GroupID.HasValue || groupallow.Contains(w.GroupID.Value));
return await request.Select(s => new ChatItemDto() return await request.Select(s => new ChatItemDto()
{ {
ID = s.ID, ID = s.ID,
@@ -237,7 +238,7 @@ namespace Hushian.Application.Services
var convModel = await _ConversationRepository.Get() var convModel = await _ConversationRepository.Get()
.Include(inc => inc.ConversationResponses) .Include(inc => inc.ConversationResponses)
.FirstOrDefaultAsync(w => w.ID == dto.ConversationID); .FirstOrDefaultAsync(w => w.ID == dto.ConversationID);
if (convModel != null) if (convModel != null)
{ {
if (ExperID.HasValue && !await _experService.CheckExperInCompany(convModel.CompanyID, ExperID.Value)) if (ExperID.HasValue && !await _experService.CheckExperInCompany(convModel.CompanyID, ExperID.Value))
@@ -277,7 +278,24 @@ namespace Hushian.Application.Services
await _ConversationRepository.UPDATE(convModel); await _ConversationRepository.UPDATE(convModel);
} }
if (dto.Type != ConversationType.UE) if (dto.Type != ConversationType.UE)
await WriteInHubFromCompany(Response.Value, convModel.UserID); await WriteInHubFromCompany(Response.Value, convModel.UserID);
else
{
var modelA = convModel.ConversationResponses.OrderBy(o => o.ID)
.LastOrDefault(l => l.Type == ConversationType.EU || l.Type == ConversationType.CU);
if (modelA != null)
{
int userid = 0;
if (modelA.Type == ConversationType.EU) userid = modelA.ExperID.Value;
else if (modelA.Type == ConversationType.CU) userid = modelA.conversation.CompanyID;
await WriteInHubFromUser(Response.Value, userid);
}
}
} }
else Response.Errors.Add("گفتگویی یافت نشد"); else Response.Errors.Add("گفتگویی یافت نشد");
@@ -360,8 +378,23 @@ namespace Hushian.Application.Services
{ {
item.IsRead = true; item.IsRead = true;
item.ReadDateTime = DateTime.Now; item.ReadDateTime = DateTime.Now;
if (!item.ExperID.HasValue)
item.ExperID = ExperID; item.ExperID = ExperID;
return (await _ConversationResponseRepository.UPDATE(item)) != null;
if( (await _ConversationResponseRepository.UPDATE(item)) != null)
{
int userID = 0;
if (item.Type == ConversationType.EU) userID = item.ExperID.Value;
else if (item.Type == ConversationType.CU) userID = item.conversation.CompanyID;
else if (item.Type == ConversationType.UE) userID = item.conversation.UserID;
await CheckMarkAsReadInHub(item.ID, userID);
return true;
}
} }
} }
@@ -380,7 +413,7 @@ namespace Hushian.Application.Services
.Select(s => new ChatItemDto() .Select(s => new ChatItemDto()
{ {
ID = s.ID, ID = s.ID,
ExperID = s.ConversationResponses.OrderBy(o => o.ID).Last(l=>l.ExperID.HasValue).ExperID, ExperID = s.ConversationResponses.OrderBy(o => o.ID).Last(l => l.ExperID.HasValue).ExperID,
ExperFullName = s.ConversationResponses.OrderBy(o => o.ID).Last(l => l.ExperID.HasValue).Exper.FullName, ExperFullName = s.ConversationResponses.OrderBy(o => o.ID).Last(l => l.ExperID.HasValue).Exper.FullName,
GroupID = s.GroupID, GroupID = s.GroupID,
GroupName = s.Group.Name, GroupName = s.Group.Name,
@@ -451,7 +484,7 @@ namespace Hushian.Application.Services
if (Response.Value != null) Response.Success = true; if (Response.Value != null) Response.Success = true;
return Response; return Response;
} }
public async Task WriteInHubFromCompany(ChatItemResponseDto item,int UserID) public async Task WriteInHubFromCompany(ChatItemResponseDto item, int UserID)
{ {
await _hubContext.Clients.User(UserID.ToString()) await _hubContext.Clients.User(UserID.ToString())
.SendAsync("ReceiveNewChatItemFromCompany", item); .SendAsync("ReceiveNewChatItemFromCompany", item);
@@ -465,19 +498,16 @@ namespace Hushian.Application.Services
// // .SendAsync("ReceiveNewConversation", conv.Id, conv.Title); // // .SendAsync("ReceiveNewConversation", conv.Id, conv.Title);
//} //}
} }
public async Task WriteInHubFromUser(ChatItemResponseDto item) public async Task WriteInHubFromUser(ChatItemResponseDto item, int UserID)
{ {
await _hubContext.Clients.User(UserID.ToString()) await _hubContext.Clients.User(UserID.ToString())
.SendAsync("ReceiveNewChatItemFromCompany", item); .SendAsync("ReceiveNewChatItemFromUser", item);
//// فرض: لیستی از کاربرانی که به گفتگو دسترسی دارند
//var usernames = new List<string>();
//foreach (var usn in usernames)
//{
// //await _hubContext.Clients.User(usn)
// // .SendAsync("ReceiveNewConversation", conv.Id, conv.Title);
//}
} }
public async Task CheckMarkAsReadInHub(int item, int UserID)
{
await _hubContext.Clients.User(UserID.ToString())
.SendAsync("CheckMarkAsRead", item);
}
} }
} }

View File

@@ -206,12 +206,12 @@ namespace Hushian.WebApi.Controllers.v1
[Authorize(Roles = "Company,User,Exper")] [Authorize(Roles = "Company,User,Exper")]
public async Task<ActionResult> MarkAsReadChatItem(int ConversationItemID) public async Task<ActionResult> MarkAsReadChatItem(int ConversationItemID)
{ {
int? ExperID = null; int? ID = null;
ConversationType Type = ConversationType.UE; ConversationType Type = ConversationType.UE;
if (User.IsInRole("Exper")) if (User.IsInRole("Exper"))
{ {
string strExperID = User.Claims.Where(w => w.Type == CustomClaimTypes.Uid).Select(s => s.Value).First(); string strExperID = User.Claims.Where(w => w.Type == CustomClaimTypes.Uid).Select(s => s.Value).First();
ExperID = Convert.ToInt32(strExperID); ID = Convert.ToInt32(strExperID);
Type = ConversationType.EU; Type = ConversationType.EU;
} }
@@ -225,7 +225,7 @@ namespace Hushian.WebApi.Controllers.v1
} }
else return Unauthorized(); else return Unauthorized();
return await _chatService.MarkAsReadChatItem(ConversationItemID, Type, ExperID) ? NoContent() return await _chatService.MarkAsReadChatItem(ConversationItemID, Type, ID) ? NoContent()
: BadRequest(new List<string>() { "خطا در بروزرسانی گفتگو" }); : BadRequest(new List<string>() { "خطا در بروزرسانی گفتگو" });
} }

View File

@@ -5,11 +5,15 @@
@using Common.Enums @using Common.Enums
@using HushianWebApp.Components @using HushianWebApp.Components
@using HushianWebApp.Service @using HushianWebApp.Service
@using HushianWebApp.Services
@using Microsoft.AspNetCore.SignalR.Client
@inject ChatService chatService @inject ChatService chatService
@inject GroupService groupService @inject GroupService groupService
@inject UserService userService @inject UserService userService
@inject IJSRuntime JS @inject IJSRuntime JS
@inject ToastService toastService @inject ToastService toastService
@inject ILocalStorageService localStorageService;
@inject HttpClient _Http;
<ConfirmDialog @ref="dialog" /> <ConfirmDialog @ref="dialog" />
<Modal @ref="modal" IsVerticallyCentered="true" IsScrollable="true" /> <Modal @ref="modal" IsVerticallyCentered="true" IsScrollable="true" />
<PageTitle>گفتمان</PageTitle> <PageTitle>گفتمان</PageTitle>
@@ -29,7 +33,7 @@
<div class="sidebar-tabs" id="A2"> <div class="sidebar-tabs" id="A2">
<!-- Inbox1 --> <!-- Inbox1 -->
<Button Outline="@isSelectedInbox1" Type="ButtonType.Link" @onclick="async()=>{await OnclickInbox(1);}" Size=ButtonSize.Small Color="ButtonColor.Warning" <Button Outline="@isSelectedInbox1" Type="ButtonType.Link" @onclick="async()=>{await OnclickInbox(1);}" Size=ButtonSize.Small Color="ButtonColor.Warning"
class=@($"tab-button inbox1-button {(isSelectedInbox1 ? "active-tab inbox1-active" : "")}")> class=@($"tab-button inbox1-button {(isSelectedInbox1 ? "active-tab inbox1-active" : "")}")>
<Icon Name="IconName.Inbox" Class="tab-icon" /> <Icon Name="IconName.Inbox" Class="tab-icon" />
<span class="tab-text">پیام های آمده</span> <span class="tab-text">پیام های آمده</span>
<Badge Color="BadgeColor.Warning" Class="tab-badge">@Inbox1Items.Count()</Badge> <Badge Color="BadgeColor.Warning" Class="tab-badge">@Inbox1Items.Count()</Badge>
@@ -37,7 +41,7 @@
<!-- Inbox2 --> <!-- Inbox2 -->
<Button Outline="@isSelectedInbox2" Type="ButtonType.Link" @onclick="async()=>{await OnclickInbox(2);}" Size=ButtonSize.Small Color="ButtonColor.Primary" <Button Outline="@isSelectedInbox2" Type="ButtonType.Link" @onclick="async()=>{await OnclickInbox(2);}" Size=ButtonSize.Small Color="ButtonColor.Primary"
class=@($"tab-button inbox2-button {(isSelectedInbox2 ? "active-tab inbox2-active" : "")}")> class=@($"tab-button inbox2-button {(isSelectedInbox2 ? "active-tab inbox2-active" : "")}")>
<Icon Name="IconName.Send" Class="tab-icon" /> <Icon Name="IconName.Send" Class="tab-icon" />
<span class="tab-text">پیام های من</span> <span class="tab-text">پیام های من</span>
<Badge Color="BadgeColor.Warning" Class="tab-badge">@Inbox2Items.Count()</Badge> <Badge Color="BadgeColor.Warning" Class="tab-badge">@Inbox2Items.Count()</Badge>
@@ -45,7 +49,7 @@
<!-- Inbox3 --> <!-- Inbox3 -->
<Button Outline="@isSelectedInbox3" Type="ButtonType.Link" @onclick="async()=>{await OnclickInbox(3);}" Size=ButtonSize.Small Color="ButtonColor.Danger" <Button Outline="@isSelectedInbox3" Type="ButtonType.Link" @onclick="async()=>{await OnclickInbox(3);}" Size=ButtonSize.Small Color="ButtonColor.Danger"
class=@($"tab-button inbox3-button {(isSelectedInbox3 ? "active-tab inbox3-active" : "")}")> class=@($"tab-button inbox3-button {(isSelectedInbox3 ? "active-tab inbox3-active" : "")}")>
<Icon Name="IconName.Archive" Class="tab-icon" /> <Icon Name="IconName.Archive" Class="tab-icon" />
<span class="tab-text">پیام های بسته</span> <span class="tab-text">پیام های بسته</span>
</Button> </Button>
@@ -63,7 +67,8 @@
<div class="item-content"> <div class="item-content">
<div class="item-header"> <div class="item-header">
<strong class="item-name">@item.UserFullName </strong> <strong class="item-name">@item.UserFullName </strong>
@if(!string.IsNullOrEmpty(item.GroupName)){ @if (!string.IsNullOrEmpty(item.GroupName))
{
<div class="mb-3"> <div class="mb-3">
<Badge Color="BadgeColor.Info" VisuallyHiddenText="Visually hidden text for Info">@item.GroupName</Badge> <Badge Color="BadgeColor.Info" VisuallyHiddenText="Visually hidden text for Info">@item.GroupName</Badge>
</div> </div>
@@ -156,20 +161,19 @@
@if (ChatCurrent.status == Common.Enums.ConversationStatus.InProgress) @if (ChatCurrent.status == Common.Enums.ConversationStatus.InProgress)
{ {
<Button Color="ButtonColor.Danger" Size=ButtonSize.ExtraSmall Outline="true" Class="finish-conversation-btn" <Button Color="ButtonColor.Danger" Size=ButtonSize.ExtraSmall Outline="true" Class="finish-conversation-btn"
@onclick="CloseChat"> @onclick="CloseChat">
<Icon Name="IconName.Escape" /> اتمام گفتگو <Icon Name="IconName.Escape" /> اتمام گفتگو
</Button> </Button>
<Button Color="ButtonColor.Secondary" Size=ButtonSize.ExtraSmall Outline="true" Class="toexper-btn" @onclick="onclickAttachedto"> <Button Color="ButtonColor.Secondary" Size=ButtonSize.ExtraSmall Outline="true" Class="toexper-btn" @onclick="onclickAttachedto">
<Icon Name="IconName.EnvelopeArrowUp" <Icon Name="IconName.EnvelopeArrowUp" /> پیوست به...
/> پیوست به...
</Button> </Button>
} }
else if (ChatCurrent.status == Common.Enums.ConversationStatus.Finished else if (ChatCurrent.status == Common.Enums.ConversationStatus.Finished
&& (CurrentUser.Role == "Company" || ChatCurrent.ExperID == CurrentUser.ExperID)) && (CurrentUser.Role == "Company" || ChatCurrent.ExperID == CurrentUser.ExperID))
{ {
<Button Color="ButtonColor.Success" Size=ButtonSize.ExtraSmall Outline="true" Class="open-conversation-btn" <Button Color="ButtonColor.Success" Size=ButtonSize.ExtraSmall Outline="true" Class="open-conversation-btn"
@onclick="OpenChat"> @onclick="OpenChat">
<Icon Name="IconName.Escape" /> باز کردن گفتگو <Icon Name="IconName.Escape" /> باز کردن گفتگو
</Button> </Button>
@@ -270,23 +274,24 @@
@code { @code {
public Common.Dtos.CurrentUserInfo CurrentUser { get; set; } Common.Dtos.CurrentUserInfo CurrentUser { get; set; }
List<Read_GroupDto> _Group = new List<Read_GroupDto>(); List<Read_GroupDto> _Group = new List<Read_GroupDto>();
//------------------------------------- //-------------------------------------
bool isSelectedInbox1 = false; bool isSelectedInbox1 = false;
public List<ChatItemDto> Inbox1Items { get; set; } = new(); List<ChatItemDto> Inbox1Items { get; set; } = new();
bool isSelectedInbox2 = true; bool isSelectedInbox2 = true;
public List<ChatItemDto> Inbox2Items { get; set; } = new(); List<ChatItemDto> Inbox2Items { get; set; } = new();
bool isSelectedInbox3 = false; bool isSelectedInbox3 = false;
public List<ChatItemDto> Inbox3Items { get; set; } = new(); List<ChatItemDto> Inbox3Items { get; set; } = new();
///////////// /////////////
public ChatItemDto? ChatCurrent { get; set; } = null; ChatItemDto? ChatCurrent { get; set; } = null;
public string MsgInput { get; set; } string MsgInput { get; set; }
bool chatloading = false; bool chatloading = false;
string SelectedChatUserName = "مهدی ربیع نژاد"; string SelectedChatUserName = "مهدی ربیع نژاد";
private bool _shouldObserveVisibility = false; private bool _shouldObserveVisibility = false;
private ConfirmDialog dialog = default!; private ConfirmDialog dialog = default!;
private Modal modal = default!; private Modal modal = default!;
private HubConnection? hubConnection;
} }
@functions { @functions {
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
@@ -297,6 +302,39 @@
Inbox2Items = await chatService.MyChatsIsInProgress(); Inbox2Items = await chatService.MyChatsIsInProgress();
Inbox3Items = await chatService.MyChatsIsFinished(); Inbox3Items = await chatService.MyChatsIsFinished();
//-------------hub
var token = await localStorageService.GetItem<string>("U/key");
string AddressHub = _Http.BaseAddress.AbsoluteUri.Replace("api/", "");
hubConnection = new HubConnectionBuilder()
.WithUrl($"{_Http.BaseAddress.AbsoluteUri.Replace("api/", "")}chatNotificationHub", options =>
{
options.AccessTokenProvider = () => Task.FromResult(token);
})
.WithAutomaticReconnect()
.Build();
hubConnection.On<ChatItemResponseDto>("ReceiveNewChatItemFromUser", async (chatitem) =>
{
if (ChatCurrent.ID == chatitem.ChatItemID)
{
ChatCurrent.Responses.Add(chatitem);
StateHasChanged();
await MarkAsRead(chatitem.ID);
// Scroll to target if exists, otherwise scroll to bottom
await JS.InvokeVoidAsync("scrollToTargetOrBottom");
}
});
hubConnection.On<int>("CheckMarkAsRead", async (chatresponseid) =>
{
if (ChatCurrent.Responses.Any(a => a.ID == chatresponseid && !a.IsRead && (a.Type == Common.Enums.ConversationType.EU || a.Type == Common.Enums.ConversationType.CU)))
{ ChatCurrent.Responses.First(a => a.ID == chatresponseid).IsRead = true; StateHasChanged(); }
});
await hubConnection.StartAsync();
//---------end hub
await base.OnInitializedAsync(); await base.OnInitializedAsync();
} }
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
@@ -339,12 +377,13 @@
if (!string.IsNullOrEmpty(MsgInput) && ChatCurrent != null) if (!string.IsNullOrEmpty(MsgInput) && ChatCurrent != null)
{ {
Common.Enums.ConversationType type = CurrentUser.Role == "Company" ? Common.Enums.ConversationType.CU : Common.Enums.ConversationType.EU; Common.Enums.ConversationType type = CurrentUser.Role == "Company" ? Common.Enums.ConversationType.CU : Common.Enums.ConversationType.EU;
await chatService.ADDChatResponse(ChatCurrent.ID, MsgInput, type); var geter= await chatService.ADDChatResponse(ChatCurrent.ID, MsgInput, type);
ChatCurrent?.Responses.Add(new() { text = MsgInput, Type = type }); if(geter!=null)
{ChatCurrent?.Responses.Add(geter);
ChatCurrent.LastText = MsgInput; ChatCurrent.LastText = MsgInput;
await Task.Yield(); await Task.Yield();
await JS.InvokeVoidAsync("scrollToBottom", "B1"); await JS.InvokeVoidAsync("scrollToBottom", "B1");
MsgInput = string.Empty; MsgInput = string.Empty;}
} }
} }
async Task onClickSelectedChat(int InboxID, ChatItemDto chatItem) async Task onClickSelectedChat(int InboxID, ChatItemDto chatItem)
@@ -375,7 +414,7 @@
async Task onclickAttachedto() async Task onclickAttachedto()
{ {
Dictionary<string, object> parameters = new Dictionary<string, object>(); Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("chatID",ChatCurrent.ID); parameters.Add("chatID", ChatCurrent.ID);
parameters.Add("OnMultipleOfThree", EventCallback.Factory.Create(this, CallBackAttachedto)); parameters.Add("OnMultipleOfThree", EventCallback.Factory.Create(this, CallBackAttachedto));
await modal.ShowAsync<AttachedtoComponent>("پیوست کارشناس", parameters: parameters); await modal.ShowAsync<AttachedtoComponent>("پیوست کارشناس", parameters: parameters);
@@ -383,12 +422,12 @@
} }
async Task CallBackAttachedto() async Task CallBackAttachedto()
{ {
await modal.HideAsync(); await modal.HideAsync();
toastService.Notify(new ToastMessage(ToastType.Success, "کارشناس جدید به این گفتگو پیوست")); toastService.Notify(new ToastMessage(ToastType.Success, "کارشناس جدید به این گفتگو پیوست"));
} }
async Task OpenChat() async Task OpenChat()
{ {
if (CurrentUser.Role == "Company" || CurrentUser.Role == "Exper" && ChatCurrent.ExperID==CurrentUser.ExperID) if (CurrentUser.Role == "Company" || CurrentUser.Role == "Exper" && ChatCurrent.ExperID == CurrentUser.ExperID)
{ {
if (ChatCurrent.status != Common.Enums.ConversationStatus.Finished) return; if (ChatCurrent.status != Common.Enums.ConversationStatus.Finished) return;
if (await chatService.OpenChat(ChatCurrent.ID)) if (await chatService.OpenChat(ChatCurrent.ID))
@@ -396,10 +435,10 @@
ChatCurrent.status = Common.Enums.ConversationStatus.InProgress; ChatCurrent.status = Common.Enums.ConversationStatus.InProgress;
StateHasChanged(); StateHasChanged();
} }
else toastService.Notify(new ToastMessage(ToastType.Danger, "تغییر وضعیت گفتگو موفق نبود")); else toastService.Notify(new ToastMessage(ToastType.Danger, "تغییر وضعیت گفتگو موفق نبود"));
} }
else toastService.Notify(new ToastMessage(ToastType.Danger, "دسترسی به این گفتگو ندارید")); else toastService.Notify(new ToastMessage(ToastType.Danger, "دسترسی به این گفتگو ندارید"));
} }
async Task CloseChat() async Task CloseChat()
{ {
@@ -455,20 +494,25 @@
overflow: hidden; overflow: hidden;
} }
.sidebar-header::before { .sidebar-header::before {
content: ''; content: '';
position: absolute; position: absolute;
top: -50%; top: -50%;
left: -50%; left: -50%;
width: 200%; width: 200%;
height: 200%; height: 200%;
background: linear-gradient(45deg, transparent 30%, rgba(255, 255, 255, 0.1) 50%, transparent 70%); background: linear-gradient(45deg, transparent 30%, rgba(255, 255, 255, 0.1) 50%, transparent 70%);
animation: shimmer 3s infinite; animation: shimmer 3s infinite;
} }
@@keyframes shimmer { @@keyframes shimmer {
0% { transform: translateX(-100%) translateY(-100%) rotate(45deg); } 0% {
100% { transform: translateX(100%) translateY(100%) rotate(45deg); } transform: translateX(-100%) translateY(-100%) rotate(45deg);
}
100% {
transform: translateX(100%) translateY(100%) rotate(45deg);
}
} }
.header-content { .header-content {
@@ -509,33 +553,33 @@
overflow: hidden; overflow: hidden;
} }
.tab-button::before { .tab-button::before {
content: ''; content: '';
position: absolute; position: absolute;
top: 0; top: 0;
left: -100%; left: -100%;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent); background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);
transition: left 0.5s ease; transition: left 0.5s ease;
} }
.tab-button:hover::before { .tab-button:hover::before {
left: 100%; left: 100%;
} }
.tab-button:hover { .tab-button:hover {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(13, 110, 253, 0.2); box-shadow: 0 6px 16px rgba(13, 110, 253, 0.2);
border-color: #0d6efd; border-color: #0d6efd;
} }
.tab-button.active-tab { .tab-button.active-tab {
background: linear-gradient(135deg, #0d6efd 0%, #0b5ed7 100%); background: linear-gradient(135deg, #0d6efd 0%, #0b5ed7 100%);
color: white; color: white;
border-color: #0d6efd; border-color: #0d6efd;
box-shadow: 0 4px 12px rgba(13, 110, 253, 0.3); box-shadow: 0 4px 12px rgba(13, 110, 253, 0.3);
} }
/* Smaller tab buttons */ /* Smaller tab buttons */
.tab-button { .tab-button {
@@ -559,11 +603,11 @@
background: linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%); background: linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%);
} }
.inbox1-button:hover { .inbox1-button:hover {
border-color: #e0a800; border-color: #e0a800;
background: linear-gradient(135deg, #ffeaa7 0%, #fdcb6e 100%); background: linear-gradient(135deg, #ffeaa7 0%, #fdcb6e 100%);
color: #856404; color: #856404;
} }
.inbox1-active { .inbox1-active {
background: linear-gradient(135deg, #ffc107 0%, #e0a800 100%) !important; background: linear-gradient(135deg, #ffc107 0%, #e0a800 100%) !important;
@@ -579,11 +623,11 @@
background: linear-gradient(135deg, #e7f1ff 0%, #cce7ff 100%); background: linear-gradient(135deg, #e7f1ff 0%, #cce7ff 100%);
} }
.inbox2-button:hover { .inbox2-button:hover {
border-color: #0b5ed7; border-color: #0b5ed7;
background: linear-gradient(135deg, #cce7ff 0%, #b3d9ff 100%); background: linear-gradient(135deg, #cce7ff 0%, #b3d9ff 100%);
color: #0b5ed7; color: #0b5ed7;
} }
.inbox2-active { .inbox2-active {
background: linear-gradient(135deg, #0d6efd 0%, #0b5ed7 100%) !important; background: linear-gradient(135deg, #0d6efd 0%, #0b5ed7 100%) !important;
@@ -599,11 +643,11 @@
background: linear-gradient(135deg, #f8d7da 0%, #f5c6cb 100%); background: linear-gradient(135deg, #f8d7da 0%, #f5c6cb 100%);
} }
.inbox3-button:hover { .inbox3-button:hover {
border-color: #c82333; border-color: #c82333;
background: linear-gradient(135deg, #f5c6cb 0%, #f1b0b7 100%); background: linear-gradient(135deg, #f5c6cb 0%, #f1b0b7 100%);
color: #721c24; color: #721c24;
} }
.inbox3-active { .inbox3-active {
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%) !important; background: linear-gradient(135deg, #dc3545 0%, #c82333 100%) !important;
@@ -639,27 +683,27 @@
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.05); box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.05);
} }
.sidebar-chat-list::-webkit-scrollbar { .sidebar-chat-list::-webkit-scrollbar {
width: 6px; width: 6px;
} }
.sidebar-chat-list::-webkit-scrollbar-track { .sidebar-chat-list::-webkit-scrollbar-track {
background: rgba(241, 241, 241, 0.5); background: rgba(241, 241, 241, 0.5);
border-radius: 10px; border-radius: 10px;
} }
.sidebar-chat-list::-webkit-scrollbar-thumb { .sidebar-chat-list::-webkit-scrollbar-thumb {
background: linear-gradient(135deg, #0d6efd 0%, #0b5ed7 100%); background: linear-gradient(135deg, #0d6efd 0%, #0b5ed7 100%);
border-radius: 10px; border-radius: 10px;
transition: all 0.3s ease; transition: all 0.3s ease;
} }
.sidebar-chat-list::-webkit-scrollbar-thumb:hover { .sidebar-chat-list::-webkit-scrollbar-thumb:hover {
background: linear-gradient(135deg, #0b5ed7 0%, #0a4b9e 100%); background: linear-gradient(135deg, #0b5ed7 0%, #0a4b9e 100%);
} }
.chat-list-item { .chat-list-item {
height:75px; height: 75px;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 1rem; padding: 1rem;
@@ -674,26 +718,26 @@
overflow: hidden; overflow: hidden;
} }
.chat-list-item::before { .chat-list-item::before {
content: ''; content: '';
position: absolute; position: absolute;
top: 0; top: 0;
left: -100%; left: -100%;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.1), transparent); background: linear-gradient(90deg, transparent, rgba(13, 110, 253, 0.1), transparent);
transition: left 0.5s ease; transition: left 0.5s ease;
} }
.chat-list-item:hover::before { .chat-list-item:hover::before {
left: 100%; left: 100%;
} }
.chat-list-item:hover { .chat-list-item:hover {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(13, 110, 253, 0.15); box-shadow: 0 6px 16px rgba(13, 110, 253, 0.15);
border-color: #0d6efd; border-color: #0d6efd;
} }
.item-avatar { .item-avatar {
width: 45px; width: 45px;
@@ -776,6 +820,7 @@
transform: scale(1); transform: scale(1);
box-shadow: 0 2px 4px rgba(220, 53, 69, 0.3); box-shadow: 0 2px 4px rgba(220, 53, 69, 0.3);
} }
50% { 50% {
transform: scale(1.05); transform: scale(1.05);
box-shadow: 0 4px 8px rgba(220, 53, 69, 0.4); box-shadow: 0 4px 8px rgba(220, 53, 69, 0.4);
@@ -1015,7 +1060,6 @@
/* Empty state styling */ /* Empty state styling */
.chat-area-container .d-flex.justify-content-center { .chat-area-container .d-flex.justify-content-center {
border-radius: 15px; border-radius: 15px;
padding: 2rem; padding: 2rem;
margin: 1rem; margin: 1rem;
@@ -1175,29 +1219,35 @@
animation: warningGlow 3s ease-in-out infinite; animation: warningGlow 3s ease-in-out infinite;
} }
.warning-note::before { .warning-note::before {
content: ''; content: '';
position: absolute; position: absolute;
top: 0; top: 0;
left: -100%; left: -100%;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 193, 7, 0.1), transparent); background: linear-gradient(90deg, transparent, rgba(255, 193, 7, 0.1), transparent);
animation: warningShimmer 4s infinite; animation: warningShimmer 4s infinite;
} }
@@keyframes warningGlow { @@keyframes warningGlow {
0%, 100% { 0%, 100% {
box-shadow: 0 4px 12px rgba(255, 193, 7, 0.2), 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 12px rgba(255, 193, 7, 0.2), 0 2px 4px rgba(0, 0, 0, 0.1);
} }
50% { 50% {
box-shadow: 0 6px 16px rgba(255, 193, 7, 0.3), 0 4px 8px rgba(0, 0, 0, 0.15); box-shadow: 0 6px 16px rgba(255, 193, 7, 0.3), 0 4px 8px rgba(0, 0, 0, 0.15);
} }
} }
@@keyframes warningShimmer { @@keyframes warningShimmer {
0% { left: -100%; } 0% {
100% { left: 100%; } left: -100%;
}
100% {
left: 100%;
}
} }
.warning-icon { .warning-icon {
@@ -1218,6 +1268,7 @@
transform: scale(1); transform: scale(1);
box-shadow: 0 4px 12px rgba(255, 193, 7, 0.4); box-shadow: 0 4px 12px rgba(255, 193, 7, 0.4);
} }
50% { 50% {
transform: scale(1.05); transform: scale(1.05);
box-shadow: 0 6px 16px rgba(255, 193, 7, 0.6); box-shadow: 0 6px 16px rgba(255, 193, 7, 0.6);
@@ -1251,9 +1302,9 @@
transition: all 0.3s ease; transition: all 0.3s ease;
} }
.warning-note:hover .warning-icon { .warning-note:hover .warning-icon {
animation: warningPulse 1s ease-in-out infinite; animation: warningPulse 1s ease-in-out infinite;
} }
/* Enhanced button styling */ /* Enhanced button styling */
.finish-conversation-btn { .finish-conversation-btn {
@@ -1291,6 +1342,7 @@
background-color: #23caba; background-color: #23caba;
color: white; color: white;
} }
.toexper-btn { .toexper-btn {
border-radius: 20px; border-radius: 20px;
font-weight: 600; font-weight: 600;
@@ -1345,7 +1397,7 @@
const targetRect = targetElement.getBoundingClientRect(); const targetRect = targetElement.getBoundingClientRect();
const containerRect = chatContainer.getBoundingClientRect(); const containerRect = chatContainer.getBoundingClientRect();
const relativeTop = targetRect.top - containerRect.top; const relativeTop = targetRect.top - containerRect.top;
// Scroll to show the target element with some padding // Scroll to show the target element with some padding
const scrollPosition = chatContainer.scrollTop + relativeTop - 100; // 100px padding const scrollPosition = chatContainer.scrollTop + relativeTop - 100; // 100px padding
chatContainer.scrollTo({ chatContainer.scrollTo({

View File

@@ -18,6 +18,7 @@
@inject ChatService chatService @inject ChatService chatService
@inject IJSRuntime JS @inject IJSRuntime JS
@inject ToastService toastService @inject ToastService toastService
@inject HttpClient _Http;
@layout UserPanelLayout @layout UserPanelLayout
<ConfirmDialog @ref="dialog" /> <ConfirmDialog @ref="dialog" />
@@ -262,14 +263,14 @@
} }
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
Console.WriteLine($"🔔 welcome");
await IsOnline(); await IsOnline();
//-------------hub //-------------hub
var token = await localStorageService.GetItem<string>("U/key"); var token = await localStorageService.GetItem<string>("U/key");
string AddressHub = _Http.BaseAddress.AbsoluteUri.Replace("api/", "");
hubConnection = new HubConnectionBuilder() hubConnection = new HubConnectionBuilder()
.WithUrl("http://localhost:5089/chatNotificationHub", options => .WithUrl($"{AddressHub}chatNotificationHub", options =>
{ {
options.AccessTokenProvider = () => Task.FromResult(token); options.AccessTokenProvider = () => Task.FromResult(token);
}) })
@@ -282,11 +283,20 @@
{ {
LastOpenChat.Responses.Add(chatitem); LastOpenChat.Responses.Add(chatitem);
StateHasChanged(); StateHasChanged();
await MarkAsRead(chatitem.ID); await MarkAsRead(chatitem.ID);
// Scroll to target if exists, otherwise scroll to bottom // Scroll to target if exists, otherwise scroll to bottom
await JS.InvokeVoidAsync("scrollToTargetOrBottom"); await JS.InvokeVoidAsync("scrollToTargetOrBottom");
} }
}); });
//-------------------------------------
hubConnection.On<int>("CheckMarkAsRead", async (chatresponseid) =>
{
if (LastOpenChat.Responses.Any(a=>a.ID==chatresponseid && !a.IsRead && a.Type==Common.Enums.ConversationType.UE))
{
LastOpenChat.Responses.First(a => a.ID == chatresponseid).IsRead = true;
StateHasChanged();
}
});
await hubConnection.StartAsync(); await hubConnection.StartAsync();
//---------end hub //---------end hub

View File

@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Components;
using System.Net; using System.Net;
using System.Net.Http.Json; using System.Net.Http.Json;
using System.Text; using System.Text;
using static System.Net.WebRequestMethods;
using static System.Runtime.InteropServices.JavaScript.JSType; using static System.Runtime.InteropServices.JavaScript.JSType;
namespace HushianWebApp.Service namespace HushianWebApp.Service
@@ -54,6 +55,7 @@ namespace HushianWebApp.Service
} }
public async Task<Tuple<string, HttpResponseMessage>> PostLogin(string route, object mode) public async Task<Tuple<string, HttpResponseMessage>> PostLogin(string route, object mode)
{ {
var result = await _Http.PostAsJsonAsync(route, mode); var result = await _Http.PostAsJsonAsync(route, mode);
if (result.IsSuccessStatusCode) if (result.IsSuccessStatusCode)
{ {