updates
This commit is contained in:
@@ -1,18 +1,13 @@
|
||||
import React from 'react';
|
||||
import { Outlet, Link, useLocation, useNavigate } from 'react-router-dom';
|
||||
import { Outlet, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
LayoutDashboard,
|
||||
Sparkles,
|
||||
LogOut,
|
||||
Menu,
|
||||
X,
|
||||
User,
|
||||
} from 'lucide-react';
|
||||
import useAuthStore from '../store/useAuthStore';
|
||||
import { useResponsive } from '../shared/hooks/useResponsive';
|
||||
|
||||
const HousekeepingLayout: React.FC = () => {
|
||||
const { isMobile } = useResponsive();
|
||||
const [sidebarOpen, setSidebarOpen] = React.useState(!isMobile);
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const { userInfo, logout } = useAuthStore();
|
||||
|
||||
@@ -25,96 +20,55 @@ const HousekeepingLayout: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const navigation = [
|
||||
{ name: 'Dashboard', href: '/housekeeping/dashboard', icon: LayoutDashboard },
|
||||
];
|
||||
|
||||
const isActive = (path: string) => location.pathname === path;
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-gradient-to-br from-slate-50 via-white to-slate-50">
|
||||
{/* Mobile menu button */}
|
||||
{isMobile && (
|
||||
<button
|
||||
onClick={() => setSidebarOpen(!sidebarOpen)}
|
||||
className="fixed top-4 left-4 z-50 p-2 bg-white rounded-lg shadow-lg"
|
||||
>
|
||||
{sidebarOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Sidebar */}
|
||||
<div
|
||||
className={`
|
||||
fixed lg:static inset-y-0 left-0 z-40
|
||||
w-64 bg-white shadow-lg transform transition-transform duration-300 ease-in-out
|
||||
${sidebarOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'}
|
||||
`}
|
||||
>
|
||||
<div className="flex flex-col h-full">
|
||||
{/* Logo/Brand */}
|
||||
<div className="flex items-center justify-between p-6 border-b border-gray-200">
|
||||
<div className="flex items-center space-x-2">
|
||||
<LayoutDashboard className="w-8 h-8 text-blue-600" />
|
||||
<span className="text-xl font-bold text-gray-900">Housekeeping</span>
|
||||
<div className="min-h-screen bg-gradient-to-br from-gray-50 via-white to-gray-50 overflow-x-hidden">
|
||||
{/* Luxury Top Navigation Bar */}
|
||||
<header className="sticky top-0 z-50 bg-white/95 backdrop-blur-xl border-b border-[#d4af37]/20 shadow-sm">
|
||||
<div className="w-full max-w-7xl mx-auto px-3 sm:px-4 md:px-6 lg:px-8">
|
||||
<div className="flex items-center justify-between h-14 sm:h-16">
|
||||
{/* Logo/Brand */}
|
||||
<div className="flex items-center space-x-2 sm:space-x-3 min-w-0 flex-1">
|
||||
<div className="relative flex-shrink-0">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-[#d4af37] to-[#c9a227] rounded-lg blur-sm opacity-50"></div>
|
||||
<div className="relative bg-gradient-to-r from-[#d4af37] to-[#c9a227] p-1.5 sm:p-2 rounded-lg">
|
||||
<Sparkles className="w-4 h-4 sm:w-5 sm:h-5 md:w-6 md:h-6 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<h1 className="text-sm sm:text-base md:text-lg lg:text-xl font-serif font-bold text-gray-900 tracking-tight truncate">
|
||||
Enterprise Housekeeping
|
||||
</h1>
|
||||
<p className="hidden xs:block text-[10px] sm:text-xs text-gray-500 font-light truncate">Luxury Management System</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Navigation */}
|
||||
<nav className="flex-1 px-4 py-6 space-y-2">
|
||||
{navigation.map((item) => {
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<Link
|
||||
key={item.name}
|
||||
to={item.href}
|
||||
onClick={() => isMobile && setSidebarOpen(false)}
|
||||
className={`
|
||||
flex items-center space-x-3 px-4 py-3 rounded-lg transition-colors
|
||||
${isActive(item.href)
|
||||
? 'bg-blue-50 text-blue-700 font-medium'
|
||||
: 'text-gray-700 hover:bg-gray-50'
|
||||
}
|
||||
`}
|
||||
>
|
||||
<Icon className="w-5 h-5" />
|
||||
<span>{item.name}</span>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
{/* User info and logout */}
|
||||
<div className="p-4 border-t border-gray-200">
|
||||
<div className="mb-3 px-4 py-2">
|
||||
<p className="text-sm font-medium text-gray-900">{userInfo?.name || userInfo?.email || 'User'}</p>
|
||||
<p className="text-xs text-gray-500 capitalize">{userInfo?.role || 'housekeeping'}</p>
|
||||
{/* User Menu */}
|
||||
<div className="flex items-center space-x-2 sm:space-x-3 flex-shrink-0">
|
||||
<div className="hidden md:flex items-center space-x-2 lg:space-x-3 px-3 lg:px-4 py-1.5 lg:py-2 rounded-lg bg-gradient-to-r from-gray-50 to-gray-100/50 border border-gray-200/50">
|
||||
<div className="w-7 h-7 lg:w-8 lg:h-8 rounded-full bg-gradient-to-br from-[#d4af37] to-[#c9a227] flex items-center justify-center flex-shrink-0">
|
||||
<User className="w-3.5 h-3.5 lg:w-4 lg:h-4 text-white" />
|
||||
</div>
|
||||
<div className="text-left min-w-0 hidden lg:block">
|
||||
<p className="text-xs lg:text-sm font-medium text-gray-900 truncate max-w-[120px]">{userInfo?.name || userInfo?.email || 'User'}</p>
|
||||
<p className="text-[10px] lg:text-xs text-gray-500 capitalize truncate">{userInfo?.role || 'housekeeping'}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="flex items-center justify-center space-x-1 sm:space-x-2 px-2 sm:px-3 md:px-4 py-1.5 sm:py-2 text-gray-700 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-all duration-200 border border-transparent hover:border-gray-200 flex-shrink-0"
|
||||
>
|
||||
<LogOut className="w-4 h-4 flex-shrink-0" />
|
||||
<span className="hidden sm:inline text-xs sm:text-sm font-medium">Logout</span>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="w-full flex items-center space-x-3 px-4 py-2 text-gray-700 hover:bg-gray-50 rounded-lg transition-colors"
|
||||
>
|
||||
<LogOut className="w-5 h-5" />
|
||||
<span>Logout</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Overlay for mobile */}
|
||||
{isMobile && sidebarOpen && (
|
||||
<div
|
||||
className="fixed inset-0 bg-black bg-opacity-50 z-30"
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
/>
|
||||
)}
|
||||
</header>
|
||||
|
||||
{/* Main content */}
|
||||
<div className="flex-1 overflow-auto lg:ml-0">
|
||||
<div className={`min-h-screen ${isMobile ? 'pt-20' : 'pt-0'}`}>
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
<main className="w-full overflow-x-hidden">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -93,12 +93,29 @@ const AccountantDashboardPage: React.FC = () => {
|
||||
totalPayments: response.data.payments.length,
|
||||
pendingPayments: pendingPayments.length,
|
||||
}));
|
||||
} else {
|
||||
// Clear data if response is not successful
|
||||
setRecentPayments([]);
|
||||
setFinancialSummary(prev => ({
|
||||
...prev,
|
||||
totalRevenue: 0,
|
||||
totalPayments: 0,
|
||||
pendingPayments: 0,
|
||||
}));
|
||||
}
|
||||
} catch (err: any) {
|
||||
// Handle AbortError silently
|
||||
if (err.name === 'AbortError') {
|
||||
return;
|
||||
}
|
||||
// Clear data when API connection fails
|
||||
setRecentPayments([]);
|
||||
setFinancialSummary(prev => ({
|
||||
...prev,
|
||||
totalRevenue: 0,
|
||||
totalPayments: 0,
|
||||
pendingPayments: 0,
|
||||
}));
|
||||
logger.error('Error fetching payments', err);
|
||||
} finally {
|
||||
setLoadingPayments(false);
|
||||
@@ -139,12 +156,29 @@ const AccountantDashboardPage: React.FC = () => {
|
||||
paidInvoices: paidInvoices.length,
|
||||
overdueInvoices: overdueInvoices.length,
|
||||
}));
|
||||
} else {
|
||||
// Clear data if response is not successful
|
||||
setRecentInvoices([]);
|
||||
setFinancialSummary(prev => ({
|
||||
...prev,
|
||||
totalInvoices: 0,
|
||||
paidInvoices: 0,
|
||||
overdueInvoices: 0,
|
||||
}));
|
||||
}
|
||||
} catch (err: any) {
|
||||
// Handle AbortError silently
|
||||
if (err.name === 'AbortError') {
|
||||
return;
|
||||
}
|
||||
// Clear data when API connection fails
|
||||
setRecentInvoices([]);
|
||||
setFinancialSummary(prev => ({
|
||||
...prev,
|
||||
totalInvoices: 0,
|
||||
paidInvoices: 0,
|
||||
overdueInvoices: 0,
|
||||
}));
|
||||
logger.error('Error fetching invoices', err);
|
||||
} finally {
|
||||
setLoadingInvoices(false);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import {
|
||||
FileText,
|
||||
Search,
|
||||
@@ -75,6 +75,10 @@ const AuditLogsPage: React.FC = () => {
|
||||
if (error.name === 'AbortError') {
|
||||
return;
|
||||
}
|
||||
// Clear data when API connection fails
|
||||
setLogs([]);
|
||||
setTotalPages(1);
|
||||
setTotalItems(0);
|
||||
logger.error('Error fetching audit logs', error);
|
||||
toast.error(error.response?.data?.message || 'Unable to load audit logs');
|
||||
} finally {
|
||||
|
||||
@@ -89,12 +89,17 @@ const DashboardPage: React.FC = () => {
|
||||
const response = await paymentService.getPayments({ page: 1, limit: 5 });
|
||||
if (response.success && response.data?.payments) {
|
||||
setRecentPayments(response.data.payments);
|
||||
} else {
|
||||
// Clear data if response is not successful
|
||||
setRecentPayments([]);
|
||||
}
|
||||
} catch (err: any) {
|
||||
// Handle AbortError silently
|
||||
if (err.name === 'AbortError') {
|
||||
return;
|
||||
}
|
||||
// Clear data when API connection fails
|
||||
setRecentPayments([]);
|
||||
logger.error('Error fetching payments', err);
|
||||
} finally {
|
||||
setLoadingPayments(false);
|
||||
@@ -125,12 +130,17 @@ const DashboardPage: React.FC = () => {
|
||||
const response = await sessionService.getMySessions();
|
||||
if (response.success && response.data?.sessions) {
|
||||
setSessions(response.data.sessions || []);
|
||||
} else {
|
||||
// Clear data if response is not successful
|
||||
setSessions([]);
|
||||
}
|
||||
} catch (err: any) {
|
||||
// Handle AbortError silently
|
||||
if (err.name === 'AbortError') {
|
||||
return;
|
||||
}
|
||||
// Clear data when API connection fails
|
||||
setSessions([]);
|
||||
logger.error('Error fetching sessions', err);
|
||||
} finally {
|
||||
setLoadingSessions(false);
|
||||
|
||||
@@ -65,9 +65,14 @@ const DashboardPage: React.FC = () => {
|
||||
const response = await paymentService.getPayments({ page: 1, limit: 5 });
|
||||
if (response.success && response.data?.payments) {
|
||||
setRecentPayments(response.data.payments);
|
||||
} else {
|
||||
// Clear data if response is not successful
|
||||
setRecentPayments([]);
|
||||
}
|
||||
} catch (err: any) {
|
||||
if (err.name !== 'AbortError') {
|
||||
// Clear data when API connection fails
|
||||
setRecentPayments([]);
|
||||
logger.error('Error fetching payments', err);
|
||||
}
|
||||
} finally {
|
||||
@@ -101,9 +106,14 @@ const DashboardPage: React.FC = () => {
|
||||
const response = await sessionService.getMySessions();
|
||||
if (response.success && response.data?.sessions) {
|
||||
setSessions(response.data.sessions || []);
|
||||
} else {
|
||||
// Clear data if response is not successful
|
||||
setSessions([]);
|
||||
}
|
||||
} catch (err: any) {
|
||||
if (err.name !== 'AbortError') {
|
||||
// Clear data when API connection fails
|
||||
setSessions([]);
|
||||
logger.error('Error fetching sessions', err);
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -80,6 +80,14 @@ const RoomListPage: React.FC = () => {
|
||||
if (err.name === 'AbortError') {
|
||||
return;
|
||||
}
|
||||
// Clear data when API connection fails
|
||||
setRooms([]);
|
||||
setPagination({
|
||||
total: 0,
|
||||
page: 1,
|
||||
limit: 12,
|
||||
totalPages: 0,
|
||||
});
|
||||
logger.error('Error fetching rooms', err);
|
||||
setError('Unable to load room list. Please try again.');
|
||||
} finally {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -218,8 +218,13 @@ const ChatManagementPage: React.FC = () => {
|
||||
const response = await chatService.listChats();
|
||||
if (response.success) {
|
||||
setChats(response.data);
|
||||
} else {
|
||||
// Clear data if response is not successful
|
||||
setChats([]);
|
||||
}
|
||||
} catch (error: any) {
|
||||
// Clear data when API connection fails
|
||||
setChats([]);
|
||||
toast.error(error.response?.data?.detail || 'Failed to load chats');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -232,8 +237,13 @@ const ChatManagementPage: React.FC = () => {
|
||||
const response = await chatService.getMessages(chatId);
|
||||
if (response.success) {
|
||||
setMessages(response.data);
|
||||
} else {
|
||||
// Clear data if response is not successful
|
||||
setMessages([]);
|
||||
}
|
||||
} catch (error: any) {
|
||||
// Clear data when API connection fails
|
||||
setMessages([]);
|
||||
toast.error(error.response?.data?.detail || 'Failed to load messages');
|
||||
} finally {
|
||||
setLoadingMessages(false);
|
||||
|
||||
@@ -73,12 +73,17 @@ const StaffDashboardPage: React.FC = () => {
|
||||
const response = await paymentService.getPayments({ page: 1, limit: 5 });
|
||||
if (response.success && response.data?.payments) {
|
||||
setRecentPayments(response.data.payments);
|
||||
} else {
|
||||
// Clear data if response is not successful
|
||||
setRecentPayments([]);
|
||||
}
|
||||
} catch (err: any) {
|
||||
// Handle AbortError silently
|
||||
if (err.name === 'AbortError') {
|
||||
return;
|
||||
}
|
||||
// Clear data when API connection fails
|
||||
setRecentPayments([]);
|
||||
logger.error('Error fetching payments', err);
|
||||
} finally {
|
||||
setLoadingPayments(false);
|
||||
@@ -109,12 +114,17 @@ const StaffDashboardPage: React.FC = () => {
|
||||
const response = await bookingService.getAllBookings({ page: 1, limit: 5 });
|
||||
if ((response.status === 'success' || response.success) && response.data?.bookings) {
|
||||
setRecentBookings(response.data.bookings);
|
||||
} else {
|
||||
// Clear data if response is not successful
|
||||
setRecentBookings([]);
|
||||
}
|
||||
} catch (err: any) {
|
||||
// Handle AbortError silently
|
||||
if (err.name === 'AbortError') {
|
||||
return;
|
||||
}
|
||||
// Clear data when API connection fails
|
||||
setRecentBookings([]);
|
||||
logger.error('Error fetching bookings', err);
|
||||
} finally {
|
||||
setLoadingBookings(false);
|
||||
|
||||
Reference in New Issue
Block a user