-imporve system
+chatwidget
This commit is contained in:
@@ -30,6 +30,7 @@ import { MainProjectContent } from './content/main-project-content/main-project-
|
|||||||
import { MainProjectAdd } from './component/main-project-add/main-project-add';
|
import { MainProjectAdd } from './component/main-project-add/main-project-add';
|
||||||
import { BudgetAprovalContent } from './content/budget-aproval-content/budget-aproval-content';
|
import { BudgetAprovalContent } from './content/budget-aproval-content/budget-aproval-content';
|
||||||
import { ThemeSwitcherComponent } from './component/theme-switcher/theme-switcher';
|
import { ThemeSwitcherComponent } from './component/theme-switcher/theme-switcher';
|
||||||
|
import { ChatWidgetComponent } from './component/chat-widget-component/chat-widget-component';
|
||||||
// import { BudgetAproval } from './component/budget-aproval/budget-aproval';
|
// import { BudgetAproval } from './component/budget-aproval/budget-aproval';
|
||||||
// import { AccDateFormatPipe } from './pipe/dtmtodatetime.pipe';
|
// import { AccDateFormatPipe } from './pipe/dtmtodatetime.pipe';
|
||||||
// import { DtmtodatetimePipe } from './dtmtodatetime.pipe';
|
// import { DtmtodatetimePipe } from './dtmtodatetime.pipe';
|
||||||
@@ -42,6 +43,7 @@ import { ThemeSwitcherComponent } from './component/theme-switcher/theme-switche
|
|||||||
SidebarComponent,
|
SidebarComponent,
|
||||||
LicensePrivacyTermsComponent,
|
LicensePrivacyTermsComponent,
|
||||||
TokenTimerComponent,
|
TokenTimerComponent,
|
||||||
|
// ChatWidgetComponent,
|
||||||
// ThemeSwitcherComponent,
|
// ThemeSwitcherComponent,
|
||||||
// BudgetAprovalContent
|
// BudgetAprovalContent
|
||||||
// MainProjectAdd,
|
// MainProjectAdd,
|
||||||
|
|||||||
@@ -184,7 +184,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 🟢 Modal Confirmation (Save / Cancel) -->
|
|
||||||
@if(showConfirmModal) {
|
@if(showConfirmModal) {
|
||||||
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm animate-fade-in">
|
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm animate-fade-in">
|
||||||
<div class="bg-white rounded-2xl shadow-2xl w-96 overflow-hidden transform transition-all scale-100">
|
<div class="bg-white rounded-2xl shadow-2xl w-96 overflow-hidden transform transition-all scale-100">
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
|
||||||
|
/* Custom Scrollbar */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #cbd5e1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fade-in-up {
|
||||||
|
animation: fadeInUp 0.3s ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px) scale(0.95);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
<div class="fixed bottom-5 right-5 z-50 flex flex-col items-end space-y-4 font-sans">
|
||||||
|
|
||||||
|
<!-- หน้าต่างแชท -->
|
||||||
|
<div *ngIf="isOpen"
|
||||||
|
class="w-80 h-96 bg-white rounded-xl shadow-2xl flex flex-col overflow-hidden border border-gray-200 animate-fade-in-up">
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="bg-red-800 p-3 flex justify-between items-center text-white shadow-md">
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<div class="relative">
|
||||||
|
<div class="w-8 h-8 bg-red-700 rounded-full flex items-center justify-center text-xs font-bold border border-red-600">
|
||||||
|
AI
|
||||||
|
</div>
|
||||||
|
<div class="absolute bottom-0 right-0 w-2.5 h-2.5 bg-green-400 border-2 border-red-800 rounded-full"></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col leading-tight">
|
||||||
|
<span class="font-bold text-sm">ผู้ช่วยวิเคราะห์ข้อมูล</span>
|
||||||
|
<span class="text-xs text-red-100">ตอบกลับภายใน 1 นาที</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button (click)="toggleChat()" class="hover:bg-red-700 p-1 rounded transition text-red-100 hover:text-white">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Chat Body -->
|
||||||
|
<div class="flex-1 p-4 overflow-y-auto bg-slate-50 space-y-3" #scrollContainer>
|
||||||
|
<div *ngFor="let msg of messages"
|
||||||
|
class="flex w-full"
|
||||||
|
[ngClass]="{'justify-end': msg.isUser, 'justify-start': !msg.isUser}">
|
||||||
|
|
||||||
|
<!-- Avatar ฝั่งซ้าย (Support) -->
|
||||||
|
<div *ngIf="!msg.isUser" class="w-6 h-6 bg-red-100 rounded-full shrink-0 mr-2 flex items-center justify-center text-xs text-red-800 font-bold self-end mb-1">
|
||||||
|
AI
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div [ngClass]="{
|
||||||
|
'bg-red-800 text-white rounded-tl-2xl rounded-tr-2xl rounded-bl-2xl': msg.isUser,
|
||||||
|
'bg-white text-gray-800 border border-gray-200 rounded-tl-2xl rounded-tr-2xl rounded-br-2xl': !msg.isUser
|
||||||
|
}"
|
||||||
|
class="max-w-[75%] px-4 py-2 text-sm shadow-sm wrap-break-words relative group">
|
||||||
|
{{ msg.text }}
|
||||||
|
<span class="text-[10px] absolute bottom-0 -mb-5 opacity-0 group-hover:opacity-100 transition-opacity text-gray-400 whitespace-nowrap"
|
||||||
|
[ngClass]="{'right-0': msg.isUser, 'left-0': !msg.isUser}">
|
||||||
|
<!-- 10:42 AM -->
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer / Input -->
|
||||||
|
<div class="p-3 bg-white border-t border-gray-200 flex items-center space-x-2">
|
||||||
|
<button class="text-gray-400 hover:text-red-800 transition">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<input type="text"
|
||||||
|
[(ngModel)]="newMessage"
|
||||||
|
(keyup.enter)="sendMessage()"
|
||||||
|
placeholder="พิมพ์ข้อความ..."
|
||||||
|
class="flex-1 bg-gray-100 rounded-full px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-red-500 focus:bg-white transition text-gray-700 placeholder-gray-400">
|
||||||
|
<button (click)="sendMessage()"
|
||||||
|
[disabled]="!newMessage.trim()"
|
||||||
|
class="text-red-950 hover:text-red-800 p-2 transition disabled:opacity-50 disabled:cursor-not-allowed">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Launcher Button -->
|
||||||
|
<button (click)="toggleChat()"
|
||||||
|
class="group w-14 h-14 bg-red-800 hover:bg-red-700 text-white rounded-full shadow-lg shadow-red-800/30 flex items-center justify-center transition-all transform hover:scale-110 focus:outline-none ring-4 ring-red-50 hover:ring-red-100 active:scale-95">
|
||||||
|
|
||||||
|
<!-- Notification Badge -->
|
||||||
|
<!-- <span *ngIf="!isOpen" class="absolute top-0 right-0 -mt-1 -mr-1 flex h-4 w-4">
|
||||||
|
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>
|
||||||
|
<span class="relative inline-flex rounded-full h-4 w-4 bg-red-500 text-[10px] items-center justify-center text-white font-bold">1</span>
|
||||||
|
</span> -->
|
||||||
|
|
||||||
|
<!-- Icon X -->
|
||||||
|
<svg *ngIf="isOpen" xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 transition-transform rotate-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<!-- Icon Chat -->
|
||||||
|
<svg *ngIf="!isOpen" xmlns="http://www.w3.org/2000/svg" class="h-7 w-7 transition-transform group-hover:-rotate-12" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-chat-widget-component',
|
||||||
|
standalone: false,
|
||||||
|
templateUrl: './chat-widget-component.html',
|
||||||
|
styleUrl: './chat-widget-component.css',
|
||||||
|
})
|
||||||
|
export class ChatWidgetComponent {
|
||||||
|
isOpen = false;
|
||||||
|
newMessage = '';
|
||||||
|
|
||||||
|
messages = [
|
||||||
|
{ text: 'สวัสดีครับ มีอะไรให้ทีมงานช่วยเหลือไหมครับ? 👋', isUser: false },
|
||||||
|
];
|
||||||
|
|
||||||
|
toggleChat() {
|
||||||
|
this.isOpen = !this.isOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMessage() {
|
||||||
|
if (this.newMessage.trim()) {
|
||||||
|
// 1. ใส่ข้อความเราลงไป
|
||||||
|
this.messages.push({ text: this.newMessage, isUser: true });
|
||||||
|
this.newMessage = '';
|
||||||
|
|
||||||
|
// 2. จำลองบอทตอบกลับ (Auto Reply Simulation)
|
||||||
|
setTimeout(() => {
|
||||||
|
this.messages.push({
|
||||||
|
text: 'ขอบคุณที่ติดต่อมาครับ ขณะนี้เจ้าหน้าที่กำลังติดลูกค้าท่านอื่น จะรีบตอบกลับให้เร็วที่สุดครับ',
|
||||||
|
isUser: false
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,6 +36,9 @@ export class MainManagerComponent implements OnInit {
|
|||||||
p.status = 'CN';
|
p.status = 'CN';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
download(){
|
||||||
|
|
||||||
|
}
|
||||||
openBudgetDetail(idx: IPrjMst) {
|
openBudgetDetail(idx: IPrjMst) {
|
||||||
this.router.navigate(['/main/manager/aproval'], {
|
this.router.navigate(['/main/manager/aproval'], {
|
||||||
state: {
|
state: {
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
<!-- <app-main-dashboard></app-main-dashboard> -->
|
<!-- <app-main-dashboard></app-main-dashboard> -->
|
||||||
<app-main-manager (documentDownload)="OnDownloadPrjDoc($event)"></app-main-manager>
|
<app-main-manager (documentDownload)="OnDownloadPrjDoc($event)"></app-main-manager>
|
||||||
|
<app-chat-widget-component></app-chat-widget-component>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { ChatWidgetComponent } from './../../component/chat-widget-component/chat-widget-component';
|
||||||
import { MainManagerContentComponent } from './../../content/main-manager-content/main-manager-content.component';
|
import { MainManagerContentComponent } from './../../content/main-manager-content/main-manager-content.component';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
@@ -36,7 +37,8 @@ import { BudgetAprovalContent } from '../../content/budget-aproval-content/budge
|
|||||||
MainProject,
|
MainProject,
|
||||||
MainProjectAdd,
|
MainProjectAdd,
|
||||||
BudgetAprovalContent,
|
BudgetAprovalContent,
|
||||||
AccDateFormatPipe
|
AccDateFormatPipe,
|
||||||
|
ChatWidgetComponent
|
||||||
// MainReportComponent
|
// MainReportComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export class GeneralService {
|
|||||||
map((res: any) => res),
|
map((res: any) => res),
|
||||||
catchError((error: any) => {
|
catchError((error: any) => {
|
||||||
const response = error?.error;
|
const response = error?.error;
|
||||||
console.error('❌ [POST Request Error]:', error);
|
// console.error('❌ [POST Request Error]:', error);
|
||||||
return throwError(() => ({
|
return throwError(() => ({
|
||||||
status: error.status,
|
status: error.status,
|
||||||
code: response?.code ?? '500',
|
code: response?.code ?? '500',
|
||||||
@@ -117,7 +117,7 @@ export class GeneralService {
|
|||||||
const options: any = {
|
const options: any = {
|
||||||
...this.getHttpOptions(true),
|
...this.getHttpOptions(true),
|
||||||
params: httpParams,
|
params: httpParams,
|
||||||
responseType: isBlob ? 'blob' : 'json' // ✅ สลับ Type ตามค่า isBlob
|
responseType: isBlob ? 'blob' : 'json'
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.http.get(fullUrl, options).pipe(
|
return this.http.get(fullUrl, options).pipe(
|
||||||
|
|||||||
Reference in New Issue
Block a user