This commit is contained in:
Iliyan Angelov
2025-11-21 09:43:54 +02:00
parent 4488e3a795
commit b56f1a6769
14 changed files with 462 additions and 225 deletions

View File

@@ -607,42 +607,6 @@ const PageContentDashboard: React.FC = () => {
{/* Home Tab */}
{activeTab === 'home' && (
<div className="space-y-8">
{/* Seed Data Button */}
<div className="bg-gradient-to-r from-purple-50 to-indigo-50 border-2 border-purple-200 rounded-xl p-4">
<div className="flex items-center justify-between">
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-1">Quick Start</h3>
<p className="text-sm text-gray-600">Load pre-configured luxury content to get started quickly</p>
</div>
<button
type="button"
onClick={() => {
if (window.confirm('This will add pre-configured luxury content to your home page. Continue?')) {
// Merge seed data with existing data, preserving all existing fields
const seedData = luxuryContentSeed.home;
setHomeData((prevData) => ({
...prevData,
// Update luxury-related fields from seed data
luxury_section_title: seedData.luxury_section_title,
luxury_section_subtitle: seedData.luxury_section_subtitle,
luxury_section_image: seedData.luxury_section_image || prevData.luxury_section_image || '',
luxury_features: Array.isArray(seedData.luxury_features) ? [...seedData.luxury_features] : [],
luxury_gallery: Array.isArray(seedData.luxury_gallery) ? [...seedData.luxury_gallery] : (prevData.luxury_gallery || []),
luxury_testimonials: Array.isArray(seedData.luxury_testimonials) ? [...seedData.luxury_testimonials] : [],
about_preview_title: seedData.about_preview_title || prevData.about_preview_title || '',
about_preview_content: seedData.about_preview_content || prevData.about_preview_content || '',
about_preview_image: seedData.about_preview_image || prevData.about_preview_image || '',
}));
toast.success('Luxury content loaded successfully! Review and save when ready.');
}
}}
className="px-6 py-2 bg-gradient-to-r from-purple-500 to-indigo-500 text-white rounded-lg font-semibold hover:from-purple-600 hover:to-indigo-600 transition-all shadow-lg"
>
Load Seed Data
</button>
</div>
</div>
{/* Home Page Content Section */}
<div className="bg-white/90 backdrop-blur-xl rounded-2xl shadow-xl border border-gray-200/50 p-8">
<h2 className="text-3xl font-extrabold text-gray-900 mb-6">Home Page Content</h2>

View File

@@ -18,7 +18,9 @@ import {
Mail,
Building2,
Upload,
Image as ImageIcon
Image as ImageIcon,
MessageCircle,
Clock
} from 'lucide-react';
import { toast } from 'react-toastify';
import adminPrivacyService, {
@@ -107,6 +109,8 @@ const SettingsPage: React.FC = () => {
company_email: '',
company_address: '',
tax_rate: 0,
chat_working_hours_start: 9,
chat_working_hours_end: 17,
});
const [logoPreview, setLogoPreview] = useState<string | null>(null);
const [faviconPreview, setFaviconPreview] = useState<string | null>(null);
@@ -234,6 +238,8 @@ const SettingsPage: React.FC = () => {
company_email: companyRes.data.company_email || '',
company_address: companyRes.data.company_address || '',
tax_rate: companyRes.data.tax_rate || 0,
chat_working_hours_start: companyRes.data.chat_working_hours_start || 9,
chat_working_hours_end: companyRes.data.chat_working_hours_end || 17,
});
@@ -2220,6 +2226,57 @@ const SettingsPage: React.FC = () => {
Default tax rate percentage to be applied to all invoices (e.g., 10 for 10%). This will be used for all bookings unless overridden.
</p>
</div>
{}
<div className="border-t border-gray-200 pt-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
<MessageCircle className="w-5 h-5 text-amber-600" />
Chat Working Hours
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-4">
<label className="flex items-center gap-2 text-sm font-bold text-gray-900 tracking-wide">
<Clock className="w-4 h-4 text-gray-600" />
Start Hour (24-hour format)
</label>
<input
type="number"
min="0"
max="23"
value={companyFormData.chat_working_hours_start || 9}
onChange={(e) =>
setCompanyFormData({ ...companyFormData, chat_working_hours_start: parseInt(e.target.value) || 9 })
}
placeholder="9"
className="w-full px-4 py-3.5 bg-white border border-gray-300 rounded-xl shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-sm"
/>
<p className="text-xs text-gray-500">
Hour when chat support becomes available (0-23, e.g., 9 for 9 AM)
</p>
</div>
<div className="space-y-4">
<label className="flex items-center gap-2 text-sm font-bold text-gray-900 tracking-wide">
<Clock className="w-4 h-4 text-gray-600" />
End Hour (24-hour format)
</label>
<input
type="number"
min="0"
max="23"
value={companyFormData.chat_working_hours_end || 17}
onChange={(e) =>
setCompanyFormData({ ...companyFormData, chat_working_hours_end: parseInt(e.target.value) || 17 })
}
placeholder="17"
className="w-full px-4 py-3.5 bg-white border border-gray-300 rounded-xl shadow-sm focus:ring-2 focus:ring-amber-500/50 focus:border-amber-500 transition-all duration-200 text-sm"
/>
<p className="text-xs text-gray-500">
Hour when chat support ends (0-23, e.g., 17 for 5 PM)
</p>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect, useRef } from 'react';
import { MessageCircle, CheckCircle, XCircle, Send, Clock, User, X } from 'lucide-react';
import { MessageCircle, CheckCircle, XCircle, Send, Clock, User, X, RefreshCw } from 'lucide-react';
import { chatService, type Chat, type ChatMessage } from '../../services/api';
import { toast } from 'react-toastify';
import { Loading, EmptyState } from '../../components/common';
@@ -311,22 +311,22 @@ const ChatManagementPage: React.FC = () => {
switch (status) {
case 'pending':
return (
<span className="px-2 py-1 text-xs font-semibold rounded-full bg-amber-100 text-amber-800 border border-amber-200">
<Clock className="w-3 h-3 inline mr-1" />
<span className="px-3 py-1.5 text-xs font-semibold rounded-full bg-gradient-to-r from-amber-50 to-yellow-50 text-amber-800 border border-amber-200/60 shadow-sm">
<Clock className="w-3 h-3 inline mr-1.5" />
Pending
</span>
);
case 'active':
return (
<span className="px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800 border border-green-200">
<CheckCircle className="w-3 h-3 inline mr-1" />
<span className="px-3 py-1.5 text-xs font-semibold rounded-full bg-gradient-to-r from-emerald-50 to-green-50 text-emerald-800 border border-emerald-200/60 shadow-sm">
<CheckCircle className="w-3 h-3 inline mr-1.5" />
Active
</span>
);
case 'closed':
return (
<span className="px-2 py-1 text-xs font-semibold rounded-full bg-gray-100 text-gray-800 border border-gray-200">
<XCircle className="w-3 h-3 inline mr-1" />
<span className="px-3 py-1.5 text-xs font-semibold rounded-full bg-gradient-to-r from-slate-50 to-gray-50 text-slate-700 border border-slate-200/60 shadow-sm">
<XCircle className="w-3 h-3 inline mr-1.5" />
Closed
</span>
);
@@ -343,22 +343,29 @@ const ChatManagementPage: React.FC = () => {
<div className="space-y-6 bg-gradient-to-br from-slate-50 via-white to-slate-50 min-h-screen -m-6 p-8">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold text-slate-900">Chat Management</h1>
<p className="text-slate-600 mt-2">Manage customer support chats</p>
<div className="flex items-center gap-3 mb-2">
<div className="h-1 w-16 bg-gradient-to-r from-[#d4af37] to-[#c9a227] rounded-full"></div>
<h1 className="text-3xl font-serif font-bold bg-gradient-to-r from-slate-900 via-slate-800 to-slate-900 bg-clip-text text-transparent tracking-tight">
Chat Management
</h1>
</div>
<p className="text-slate-600 mt-2 font-light">Manage customer support chats with elegance</p>
</div>
<button
onClick={fetchChats}
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
className="px-6 py-2.5 bg-gradient-to-r from-[#d4af37] to-[#c9a227] text-slate-900 rounded-lg font-semibold hover:from-[#c9a227] hover:to-[#d4af37] transition-all duration-300 shadow-lg shadow-[#d4af37]/30 hover:shadow-xl hover:shadow-[#d4af37]/40 flex items-center gap-2"
>
<RefreshCw className="w-4 h-4" />
Refresh
</button>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 h-[calc(100vh-200px)]">
{}
<div className="bg-white rounded-xl shadow-lg border border-gray-200 overflow-hidden flex flex-col">
<div className="p-4 border-b border-gray-200 bg-gradient-to-r from-blue-50 to-blue-100">
<h2 className="font-semibold text-slate-900">Chats ({chats.length})</h2>
<div className="luxury-glass rounded-xl shadow-2xl border border-[#d4af37]/20 overflow-hidden flex flex-col">
<div className="p-4 border-b border-[#d4af37]/20 bg-gradient-to-r from-[#d4af37]/10 via-[#c9a227]/10 to-[#d4af37]/10 relative">
<div className="absolute top-0 left-0 right-0 h-0.5 bg-gradient-to-r from-transparent via-[#d4af37] to-transparent"></div>
<h2 className="font-serif font-semibold text-slate-900 tracking-wide">Chats ({chats.length})</h2>
</div>
<div className="flex-1 overflow-y-auto">
{chats.length === 0 ? (
@@ -372,8 +379,10 @@ const ChatManagementPage: React.FC = () => {
<div
key={chat.id}
onClick={() => setSelectedChat(chat)}
className={`p-4 cursor-pointer hover:bg-gray-50 transition-colors ${
selectedChat?.id === chat.id ? 'bg-blue-50 border-l-4 border-blue-600' : ''
className={`p-4 cursor-pointer hover:bg-gradient-to-r hover:from-[#d4af37]/5 hover:to-[#c9a227]/5 transition-all duration-300 ${
selectedChat?.id === chat.id
? 'bg-gradient-to-r from-[#d4af37]/10 to-[#c9a227]/10 border-l-4 border-[#d4af37] shadow-sm'
: 'border-l-4 border-transparent'
}`}
>
<div className="flex items-start justify-between mb-2">
@@ -397,7 +406,7 @@ const ChatManagementPage: React.FC = () => {
e.stopPropagation();
handleAcceptChat(chat.id);
}}
className="mt-2 w-full px-3 py-1.5 bg-green-600 text-white text-sm rounded-lg hover:bg-green-700 transition-colors"
className="mt-2 w-full px-3 py-1.5 bg-gradient-to-r from-[#d4af37] to-[#c9a227] text-slate-900 text-sm font-semibold rounded-lg hover:from-[#c9a227] hover:to-[#d4af37] transition-all duration-300 shadow-md shadow-[#d4af37]/30 hover:shadow-lg"
>
Accept Chat
</button>
@@ -410,19 +419,20 @@ const ChatManagementPage: React.FC = () => {
</div>
{}
<div className="lg:col-span-2 bg-white rounded-xl shadow-lg border border-gray-200 overflow-hidden flex flex-col">
<div className="lg:col-span-2 luxury-glass rounded-xl shadow-2xl border border-[#d4af37]/20 overflow-hidden flex flex-col">
{selectedChat ? (
<>
<div className="p-4 border-b border-gray-200 bg-gradient-to-r from-blue-50 to-blue-100 flex items-center justify-between">
<div className="p-4 border-b border-[#d4af37]/20 bg-gradient-to-r from-[#d4af37]/10 via-[#c9a227]/10 to-[#d4af37]/10 flex items-center justify-between relative">
<div className="absolute top-0 left-0 right-0 h-0.5 bg-gradient-to-r from-transparent via-[#d4af37] to-transparent"></div>
<div>
<h2 className="font-semibold text-slate-900">
<h2 className="font-serif font-semibold text-slate-900 tracking-wide">
{selectedChat.visitor_name || 'Guest'}
</h2>
{selectedChat.visitor_email && (
<p className="text-sm text-gray-600">{selectedChat.visitor_email}</p>
<p className="text-sm text-slate-600 font-light">{selectedChat.visitor_email}</p>
)}
{selectedChat.staff_name && (
<p className="text-xs text-blue-600 mt-1">
<p className="text-xs text-[#d4af37] mt-1 font-medium">
Accepted by: {selectedChat.staff_name}
</p>
)}
@@ -441,7 +451,7 @@ const ChatManagementPage: React.FC = () => {
</div>
</div>
<div className="flex-1 overflow-y-auto p-4 space-y-4 bg-gray-50">
<div className="flex-1 overflow-y-auto p-4 space-y-4 bg-gradient-to-b from-slate-50/50 to-white">
{loadingMessages ? (
<Loading text="Loading messages..." />
) : messages.length === 0 ? (
@@ -458,25 +468,25 @@ const ChatManagementPage: React.FC = () => {
}`}
>
<div
className={`max-w-[80%] rounded-lg p-3 ${
className={`max-w-[80%] rounded-xl p-4 shadow-lg ${
message.sender_type === 'staff'
? 'bg-blue-600 text-white'
: 'bg-white text-gray-800 border border-gray-200'
? 'bg-gradient-to-br from-[#d4af37] to-[#c9a227] text-slate-900 border border-[#d4af37]/30'
: 'bg-white text-slate-800 border border-slate-200/60 shadow-sm'
}`}
>
{message.sender_type === 'visitor' && (
<div className="text-xs font-semibold mb-1 text-blue-600">
<div className="text-xs font-semibold mb-1.5 text-[#d4af37] tracking-wide">
{message.sender_name || 'Visitor'}
</div>
)}
<p className="text-sm whitespace-pre-wrap break-words">
<p className="text-sm whitespace-pre-wrap break-words leading-relaxed">
{message.message}
</p>
<p
className={`text-xs mt-1 ${
className={`text-xs mt-2 ${
message.sender_type === 'staff'
? 'text-blue-100'
: 'text-gray-500'
? 'text-slate-700/70'
: 'text-slate-500'
}`}
>
{new Date(message.created_at).toLocaleTimeString([], {
@@ -492,27 +502,27 @@ const ChatManagementPage: React.FC = () => {
</div>
{selectedChat.status !== 'closed' && (
<div className="p-4 border-t border-gray-200 bg-white">
<div className="flex gap-2">
<div className="p-4 border-t border-[#d4af37]/20 bg-white/90 backdrop-blur-sm">
<div className="flex gap-3">
<input
type="text"
value={newMessage}
onChange={(e) => setNewMessage(e.target.value)}
onKeyPress={handleKeyPress}
placeholder="Type your message..."
className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
className="flex-1 px-4 py-3 border-2 border-slate-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-[#d4af37]/30 focus:border-[#d4af37] transition-all duration-200 font-light"
disabled={selectedChat.status === 'pending'}
/>
<button
onClick={handleSendMessage}
disabled={!newMessage.trim() || selectedChat.status === 'pending'}
className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 disabled:bg-gray-300 disabled:cursor-not-allowed transition-colors flex items-center gap-2"
className="bg-gradient-to-r from-[#d4af37] to-[#c9a227] text-slate-900 px-5 py-3 rounded-xl hover:from-[#c9a227] hover:to-[#d4af37] disabled:bg-gray-300 disabled:text-gray-500 disabled:cursor-not-allowed transition-all duration-300 flex items-center gap-2 font-semibold shadow-lg shadow-[#d4af37]/30 hover:shadow-xl disabled:shadow-none"
>
<Send className="w-4 h-4" />
</button>
</div>
{selectedChat.status === 'pending' && (
<p className="text-xs text-gray-500 mt-2">
<p className="text-xs text-slate-500 mt-2 font-light">
Accept the chat to start messaging
</p>
)}