-complete mockup
This commit is contained in:
@@ -1,96 +1,122 @@
|
||||
<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">
|
||||
<!-- Container หลัก: Fixed มุมขวาล่าง -->
|
||||
<div class="fixed bottom-5 right-5 z-50 flex flex-col items-end space-y-4 font-sans">
|
||||
|
||||
<!-- 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">
|
||||
<!-- หน้าต่างแชท -->
|
||||
<!-- เราใช้ [style.width] และ [style.height] เพื่อควบคุมขนาดจากการยืดขยาย -->
|
||||
<div *ngIf="isOpen"
|
||||
[style.width.px]="isMaximized ? 600 : chatWidth"
|
||||
[style.height.px]="isMaximized ? 800 : chatHeight"
|
||||
class="bg-white rounded-xl shadow-2xl flex flex-col overflow-hidden border border-gray-200 animate-fade-in-up relative transition-all duration-100 ease-out"
|
||||
[class.max-w-[90vw]]="isMaximized"
|
||||
[class.max-h-[80vh]]="isMaximized">
|
||||
|
||||
<!-- ============================== -->
|
||||
<!-- 1. ส่วนจุดดึงขยาย (Resize Handle) -->
|
||||
<!-- ============================== -->
|
||||
<!-- แสดงเฉพาะตอนไม่ได้ Maximize -->
|
||||
<div *ngIf="!isMaximized"
|
||||
(mousedown)="startResizing($event)"
|
||||
class="absolute top-0 left-0 w-6 h-6 z-50 cursor-nw-resize flex items-start justify-start p-1 opacity-0 hover:opacity-100 transition-opacity group">
|
||||
<!-- Visual Indicator (มุมสามเหลี่ยมเล็กๆ) -->
|
||||
<div class="w-2 h-2 border-t-2 border-l-2 border-red-400 rounded-tl-sm"></div>
|
||||
</div>
|
||||
|
||||
<!-- Header -->
|
||||
<div class="bg-red-800 p-3 flex justify-between items-center text-white shadow-md shrink-0 cursor-default select-none">
|
||||
<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">ตอบกลับทันที</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ปุ่มควบคุม (Control Buttons) -->
|
||||
<div class="flex items-center space-x-1">
|
||||
|
||||
<!-- 2. ปุ่ม Maximize / Minimize -->
|
||||
<button (click)="toggleMaximize()" class="hover:bg-red-700 p-1 rounded transition text-red-100 hover:text-white" title="ขยาย/ย่อ">
|
||||
<!-- Icon: Maximize -->
|
||||
<svg *ngIf="!isMaximized" xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" />
|
||||
</svg>
|
||||
<!-- Icon: Minimize -->
|
||||
<svg *ngIf="isMaximized" xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 9V4.5M9 9H4.5M9 9l-6-6M15 9V4.5M15 9h4.5M15 9l6-6M9 15v4.5M9 15H4.5M9 15l-6 6M15 15v4.5M15 15h4.5M15 15l6 6" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- ปุ่ม Close -->
|
||||
<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>
|
||||
</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}">
|
||||
|
||||
<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 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 [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-[85%] px-4 py-2 text-sm shadow-sm wrap-break-words relative group">
|
||||
{{ msg.text }}
|
||||
</div>
|
||||
</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>
|
||||
<!-- Footer / Input -->
|
||||
<div class="p-3 bg-white border-t border-gray-200 flex items-center space-x-2 shrink-0">
|
||||
<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>
|
||||
|
||||
<!-- 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>
|
||||
<!-- 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">
|
||||
|
||||
<svg *ngIf="isOpen" 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="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
|
||||
<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>
|
||||
|
||||
<!-- 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>
|
||||
<!-- Overlay กันกดส่วนอื่นตอนกำลังลาก (Optional) -->
|
||||
<div *ngIf="isResizing" class="fixed inset-0 z-[60] cursor-nw-resize"></div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { ReactiveFormsModule, FormGroup, FormControl, Validators } from '@angular/forms';
|
||||
import { Component, HostListener } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-chat-widget-component',
|
||||
@@ -7,30 +8,94 @@ import { Component } from '@angular/core';
|
||||
styleUrl: './chat-widget-component.css',
|
||||
})
|
||||
export class ChatWidgetComponent {
|
||||
isOpen = false;
|
||||
isOpen = true;
|
||||
newMessage = '';
|
||||
chatForm!: FormGroup;
|
||||
|
||||
// State สำหรับจัดการขนาด
|
||||
isMaximized = false;
|
||||
isResizing = false;
|
||||
|
||||
// ขนาดเริ่มต้น (px)
|
||||
chatWidth = 320;
|
||||
chatHeight = 384;
|
||||
|
||||
// ตัวแปรสำหรับคำนวณการลาก
|
||||
private startX = 0;
|
||||
private startY = 0;
|
||||
private startWidth = 0;
|
||||
private startHeight = 0;
|
||||
|
||||
|
||||
constructor() {
|
||||
this.setupFormControl(); // เรียกใช้ตอนเริ่ม Component
|
||||
}
|
||||
|
||||
setupFormControl() {
|
||||
this.chatForm = new FormGroup({
|
||||
message: new FormControl('', [Validators.required])
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
messages = [
|
||||
{ text: 'สวัสดีครับ มีอะไรให้ทีมงานช่วยเหลือไหมครับ? 👋', isUser: false },
|
||||
{ text: 'สวัสดีครับ AI พร้อมช่วยเหลือครับ 👋', isUser: false },
|
||||
{ text: 'ลองกดปุ่มขยายที่หัวมุม หรือลากมุมซ้ายบนของกล่องเพื่อปรับขนาดได้เลยครับ', isUser: false },
|
||||
];
|
||||
|
||||
toggleChat() {
|
||||
this.isOpen = !this.isOpen;
|
||||
this.isMaximized = false; // Reset ขนาดเมื่อปิด
|
||||
}
|
||||
|
||||
toggleMaximize() {
|
||||
this.isMaximized = !this.isMaximized;
|
||||
}
|
||||
|
||||
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: 'ขอบคุณที่ติดต่อมาครับ ขณะนี้เจ้าหน้าที่กำลังติดลูกค้าท่านอื่น จะรีบตอบกลับให้เร็วที่สุดครับ',
|
||||
text: 'รับทราบครับ ระบบกำลังประมวลผล...',
|
||||
isUser: false
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Logic สำหรับการ Resize (ลากขยาย) ---
|
||||
|
||||
startResizing(event: MouseEvent) {
|
||||
event.preventDefault(); // ป้องกันการเลือก Text
|
||||
this.isResizing = true;
|
||||
|
||||
// บันทึกตำแหน่งเมาส์และขนาดปัจจุบัน
|
||||
this.startX = event.clientX;
|
||||
this.startY = event.clientY;
|
||||
this.startWidth = this.chatWidth;
|
||||
this.startHeight = this.chatHeight;
|
||||
}
|
||||
|
||||
// ใช้ @HostListener เพื่อดักจับ MouseMove ทั่วทั้ง Window
|
||||
@HostListener('window:mousemove', ['$event'])
|
||||
onMouseMove(event: MouseEvent) {
|
||||
if (!this.isResizing) return;
|
||||
|
||||
// คำนวณความต่าง (Delta)
|
||||
// หมายเหตุ: ลากไปทางซ้าย (ค่า X น้อยลง) ต้องทำให้กว้างขึ้น -> ใช้ startX - clientX
|
||||
// ลากไปข้างบน (ค่า Y น้อยลง) ต้องทำให้สูงขึ้น -> ใช้ startY - clientY
|
||||
const deltaX = this.startX - event.clientX;
|
||||
const deltaY = this.startY - event.clientY;
|
||||
|
||||
// กำหนดขนาดใหม่ (จำกัดขนาดต่ำสุดไม่ให้เล็กเกินไป)
|
||||
this.chatWidth = Math.max(300, this.startWidth + deltaX);
|
||||
this.chatHeight = Math.max(350, this.startHeight + deltaY);
|
||||
}
|
||||
|
||||
@HostListener('window:mouseup')
|
||||
stopResizing() {
|
||||
this.isResizing = false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user