@page "/UserCP/{CompanyID:int}" @page "/UserCP/{CompanyID:int}/{ChatID:int?}" @using Common.Dtos.Company @using Common.Dtos.Conversation @using Common.Dtos.Group @using HushianWebApp.Service @using HushianWebApp.Services @using Microsoft.AspNetCore.SignalR.Client; @using System.Threading; گفتگو با @CompanyInfo.FullName @inject NavigationManager NavigationManager @inject ChatService ChatService @inject ILocalStorageService localStorageService; @inject AuthService authService; @inject BaseController baseController; @inject CompanyService companyService @inject UserService userService @inject GroupService groupService @inject ChatService chatService @inject IJSRuntime JS @inject ToastService toastService @inject HttpClient _Http; @layout UserPanelLayout
@if (IsEndFirstProcess) { @if (IsLogin) {
@if (LastOpenChat != null) {

@ExperYou

}
@if (LastOpenChat != null) { @if (LastOpenChat.status == Common.Enums.ConversationStatus.InProgress) { } }
@if (LastOpenChat != null && LastOpenChat.Responses != null) {
@{ bool target = false; } @foreach (var msg in LastOpenChat?.Responses) { @if (!target && ((!msg.IsRead && msg.Type != Common.Enums.ConversationType.UE) || LastOpenChat.Responses.Last() == msg)) { target = true;
@if (!msg.IsRead && msg.Type != Common.Enums.ConversationType.UE) {
پیام جدید
}
}
@if (msg.FileContent != null && msg.FileContent.Length > 0 && !string.IsNullOrWhiteSpace(msg.FileType)) { @if (msg.FileType.StartsWith("image/")) { image } else if (msg.FileType.StartsWith("audio/")) {
} @if (!string.IsNullOrWhiteSpace(msg.text)) {
@msg.text
} } else { @msg.text }
@if (msg.Type == Common.Enums.ConversationType.UE) { if (msg.IsRead) { } else { } }
} @{ target = false; }
} else {

هوشیان

@if (CompanyGroups != null && CompanyGroups.Any()) {
انتخاب گروه:
@foreach (var group in CompanyGroups) {
@if (group.img == null || group.img.Length == 0) { } else { Uploaded Image } @group.Name
}
}
}
@if (LastOpenChat == null || (LastOpenChat != null && LastOpenChat.status != Common.Enums.ConversationStatus.Finished && LastOpenChat.Responses != null)) {
@if (SelectedImagePreview != null) {
preview
} @if (RecordedAudioBytes != null) {
@RecordedAudioDuration
} @if (IsRecording) {
در حال ضبط... @RecordingTime
}
}
} else {
} } else {

در حال بررسی وضعیت ...

}
@code { [Parameter] public int CompanyID { get; set; } [Parameter] public int? ChatID { get; set; } private ConfirmDialog dialog = default!; private HubConnection? hubConnection; private bool _shouldObserveVisibility = false; int? GroupID = null; ReadANDUpdate_CompanyDto? CompanyInfo = new(); Common.Dtos.CurrentUserInfo CurrentUser = new(); List CompanyGroups = new(); ChatItemDto? LastOpenChat = new(); string MsgInput = string.Empty; IBrowserFile? SelectedImageFile = null; byte[]? SelectedImageBytes = null; string? SelectedImagePreview = null; // Audio recording properties bool IsRecording = false; string RecordingTime = "00:00"; byte[]? RecordedAudioBytes = null; string? RecordedAudioUrl = null; string RecordedAudioDuration = "00:00"; private System.Threading.Timer? recordingTimer; private DateTime recordingStartTime; bool chatloading = false; public bool IsLogin { get; set; } = false; public bool IsEndFirstProcess { get; set; } = false; string ExperYou { get { if (CompanyInfo == null) return string.Empty; string value = $"{CompanyInfo.FullName}"; if (GroupID.HasValue) { value += "/" + CompanyGroups.FirstOrDefault(f => f.ID == GroupID.GetValueOrDefault()).Name; } if (LastOpenChat != null && LastOpenChat.Responses!=null) { var model = LastOpenChat.Responses.OrderBy(o => o.ID).LastOrDefault(l => l.Type != Common.Enums.ConversationType.UE); if (model!=null && model.Type==Common.Enums.ConversationType.CU && !string.IsNullOrEmpty(CompanyInfo.FullNameManager)) { value += "/" + CompanyInfo.FullNameManager; } else if (!string.IsNullOrEmpty(LastOpenChat.ExperFullName)) value += "/" + LastOpenChat.ExperFullName; } return value; } } // Audio recording methods private async Task ToggleAudioRecording() { if (IsRecording) { await StopAudioRecording(); } else { await StartAudioRecording(); } } private async Task StartAudioRecording() { try { var result = await JS.InvokeAsync("startAudioRecording"); if (result) { IsRecording = true; recordingStartTime = DateTime.Now; recordingTimer = new System.Threading.Timer(UpdateRecordingTime, null, 0, 1000); StateHasChanged(); } else { toastService.Notify(new ToastMessage(ToastType.Warning, "خطا در شروع ضبط صدا")); } } catch (Exception ex) { toastService.Notify(new ToastMessage(ToastType.Danger, $"خطا در ضبط صدا: {ex.Message}")); } } private async Task StopAudioRecording() { try { var audioData = await JS.InvokeAsync("stopAudioRecording"); if (!string.IsNullOrEmpty(audioData)) { // Convert base64 to bytes var base64Data = audioData.Split(',')[1]; RecordedAudioBytes = Convert.FromBase64String(base64Data); RecordedAudioUrl = audioData; RecordedAudioDuration = RecordingTime; IsRecording = false; recordingTimer?.Dispose(); recordingTimer = null; await ClearSelectedImage(); StateHasChanged(); } } catch (Exception ex) { toastService.Notify(new ToastMessage(ToastType.Danger, $"خطا در توقف ضبط صدا: {ex.Message}")); } finally { IsRecording = false; recordingTimer?.Dispose(); recordingTimer = null; StateHasChanged(); } } private void UpdateRecordingTime(object? state) { var elapsed = DateTime.Now - recordingStartTime; RecordingTime = $"{elapsed.Minutes:D2}:{elapsed.Seconds:D2}"; InvokeAsync(StateHasChanged); } private Task ClearRecordedAudio() { RecordedAudioBytes = null; RecordedAudioUrl = null; RecordedAudioDuration = "00:00"; return Task.CompletedTask; } private string GetAudioDataUrl(string? fileType, byte[]? content) => (string.IsNullOrWhiteSpace(fileType) || content == null || content.Length == 0) ? string.Empty : $"data:{fileType};base64,{Convert.ToBase64String(content)}"; } @functions { async Task OnClickSendMsg() { if (!string.IsNullOrEmpty(MsgInput) || SelectedImageFile != null || RecordedAudioBytes != null) { if (LastOpenChat != null) { Common.Enums.ConversationType type = Common.Enums.ConversationType.UE; ChatItemResponseDto? model; if (SelectedImageFile != null) { var bytes = SelectedImageBytes ?? Array.Empty(); model = await chatService.ADDChatResponse( LastOpenChat.ID, MsgInput, type, SelectedImageFile.Name, SelectedImageFile.ContentType, bytes); } else if (RecordedAudioBytes != null) { // Send audio message var fileName = $"audio_{DateTimeOffset.Now.ToUnixTimeSeconds()}.wav"; model = await chatService.ADDChatResponse( LastOpenChat.ID, MsgInput, type, fileName, "audio/wav", RecordedAudioBytes); } else { model = await chatService.ADDChatResponse(LastOpenChat.ID, MsgInput, type); } LastOpenChat?.Responses.Add(model); LastOpenChat.LastText = MsgInput; } else { //TODO New Chat var model = await chatService.NewChatFromCurrentUser(new ADD_ConversationDto() { CompanyID = CompanyID, GroupID = GroupID, Question = MsgInput, UserID = 0 }); if (model != null) { LastOpenChat = model; } else toastService.Notify(new ToastMessage(ToastType.Danger, "خطا در گفتگو جدید")); } await Task.Yield(); // Scroll to bottom for user's own messages await JS.InvokeVoidAsync("scrollToBottom", "B1"); MsgInput = string.Empty; SelectedImageFile = null; SelectedImageBytes = null; SelectedImagePreview = null; // Clear recorded audio after sending RecordedAudioBytes = null; RecordedAudioUrl = null; RecordedAudioDuration = "00:00"; } } protected override async Task OnAfterRenderAsync(bool firstRender) { if (_shouldObserveVisibility) { _shouldObserveVisibility = false; await JS.InvokeVoidAsync("observeVisibility", DotNetObjectReference.Create(this)); } } protected override async Task OnInitializedAsync() { await IsOnline(); //-------------hub var token = await localStorageService.GetItem("U/key"); string AddressHub = _Http.BaseAddress.AbsoluteUri.Replace("api/", ""); hubConnection = new HubConnectionBuilder() .WithUrl($"{AddressHub}chatNotificationHub", options => { options.AccessTokenProvider = () => Task.FromResult(token); }) .WithAutomaticReconnect() .Build(); hubConnection.On("ReceiveNewChatItemFromCompany",async (chatitem) => { if (LastOpenChat.ID == chatitem.ChatItemID) { LastOpenChat.Responses.Add(chatitem); StateHasChanged(); await MarkAsRead(chatitem.ID); // Scroll to target if exists, otherwise scroll to bottom await JS.InvokeVoidAsync("scrollToTargetOrBottom"); } }); //------------------------------------- hubConnection.On("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(); //---------end hub await base.OnInitializedAsync(); } async Task IsOnline() { var token = await localStorageService.GetItem("U/key"); if (string.IsNullOrEmpty(token)) { IsLogin = false; IsEndFirstProcess = true; } else { await baseController.RemoveToken(); await baseController.SetToken(token); if (!await authService.IsOnline()) { await baseController.RemoveToken(); IsLogin = false; IsEndFirstProcess = true; } else { IsEndFirstProcess = true; await Afterlogin(); } } } async Task Afterlogin() { IsLogin = true; CurrentUser = await userService.GetCurrentUserInfo(); await IsCompany(); } async Task IsCompany() { CompanyInfo = await companyService.GetCompany(CompanyID); if (CompanyInfo != null) CompanyGroups = await groupService.GetGroupsCompany(CompanyID); await IsLastChat(); } async Task IsLastChat() { if (CompanyInfo != null) { if (ChatID.HasValue) LastOpenChat = await ChatService.Getchat(ChatID.GetValueOrDefault()); else LastOpenChat = LastOpenChat = await ChatService.GetLastOpenChatInCompany(CompanyID); if (LastOpenChat != null) { GroupID = LastOpenChat.GroupID; // Always set up visibility observation for chat bubbles _shouldObserveVisibility = true; StateHasChanged(); // Wait for render to complete await Task.Delay(200); // Scroll to target if exists, otherwise scroll to bottom await JS.InvokeVoidAsync("scrollToTargetOrBottom"); } } } [JSInvokable] public async Task MarkAsRead(int id) { var msg = LastOpenChat.Responses.FirstOrDefault(m => m.ID == id); if (msg != null && !msg.IsRead && msg.Type != Common.Enums.ConversationType.UE) { msg.IsRead = true; await chatService.MarkAsReadChatItemAsync(id); // StateHasChanged(); } await Task.CompletedTask; } // Method to handle new messages from other users public async Task HandleNewMessage() { if (LastOpenChat?.Responses != null) { var hasUnreadMessages = LastOpenChat.Responses.Any(m => !m.IsRead && m.Type != Common.Enums.ConversationType.UE); if (hasUnreadMessages) { await JS.InvokeVoidAsync("autoScrollToNewMessage"); } } } async Task NewChat() { LastOpenChat = null; } async Task CloseChat() { var options = new ConfirmDialogOptions { YesButtonText = "بله", YesButtonColor = ButtonColor.Success, NoButtonText = "انصراف", NoButtonColor = ButtonColor.Danger }; var confirmation = await dialog.ShowAsync( title: "پایان دادن به گفتگو", message1: "اطمینان دارید ؟", confirmDialogOptions: options); if (confirmation) { if (await chatService.ChatIsFinishFromUser(LastOpenChat.ID)) { LastOpenChat.status = Common.Enums.ConversationStatus.Finished; StateHasChanged(); } } } async Task Logout() { await baseController.RemoveToken(); await localStorageService.RemoveItem("U/key"); IsLogin = false; StateHasChanged(); } async Task SelectGroup(int groupId) { GroupID = groupId; StateHasChanged(); } private string GetImageSource(byte[] bytes) => $"data:image/jpeg;base64,{Convert.ToBase64String(bytes)}"; private async Task HandleKeyDown(KeyboardEventArgs e) { if (e.Key == "Enter") await OnClickSendMsg(); } } @functions { private async Task OpenFileDialog() { await JS.InvokeVoidAsync("triggerClick", "chatImageInput"); } private async Task OnImageSelected(InputFileChangeEventArgs e) { var file = e.File; if (file is null) { SelectedImageFile = null; SelectedImageBytes = null; SelectedImagePreview = null; return; } SelectedImageFile = file; using var memoryStream = new MemoryStream(); await file.OpenReadStream().CopyToAsync(memoryStream); SelectedImageBytes = memoryStream.ToArray(); SelectedImagePreview = $"data:{file.ContentType};base64,{Convert.ToBase64String(SelectedImageBytes)}"; await ClearRecordedAudio(); } private Task ClearSelectedImage() { SelectedImageFile = null; SelectedImageBytes = null; SelectedImagePreview = null; return Task.CompletedTask; } private static string GetImageDataUrl(string? fileType, byte[]? content) => (string.IsNullOrWhiteSpace(fileType) || content == null || content.Length == 0) ? string.Empty : $"data:{fileType};base64,{Convert.ToBase64String(content)}"; private static string GetDownloadFileName(string? fileName, string? fileType) { if (!string.IsNullOrWhiteSpace(fileName)) return fileName; var ext = ""; if (!string.IsNullOrWhiteSpace(fileType) && fileType.StartsWith("image/")) { ext = "." + fileType.Split('/').Last(); } return $"image_{DateTimeOffset.Now.ToUnixTimeSeconds()}{ext}"; } }