From 4cbcdde3691bb2be07177cef65e2150e9001d3e7 Mon Sep 17 00:00:00 2001 From: Iliyan Angelov Date: Fri, 12 Dec 2025 16:22:41 +0200 Subject: [PATCH] updates --- .../content/pages/CancellationPolicyPage.tsx | 2 +- .../features/content/pages/ContactPage.tsx | 2 +- .../content/pages/PrivacyPolicyPage.tsx | 3 +- .../content/pages/RefundsPolicyPage.tsx | 3 +- .../content/pages/ServiceDetailPage.tsx | 32 +-- .../features/content/pages/ServicesPage.tsx | 24 ++- .../src/features/content/pages/TermsPage.tsx | 3 +- .../components/CreateBookingModal.tsx | 3 +- .../components/CreateGroupBookingModal.tsx | 1 + .../components/HousekeepingManagement.tsx | 13 +- .../components/InspectionManagement.tsx | 5 +- .../components/MaintenanceManagement.tsx | 5 +- .../loyalty/services/loyaltyService.ts | 4 +- .../notifications/components/ChatWidget.tsx | 1 + .../components/InAppNotificationBell.tsx | 5 +- .../components/NotificationPreferences.tsx | 1 + .../components/NotificationTemplatesModal.tsx | 1 + .../components/SendNotificationModal.tsx | 1 + .../components/BoricaPaymentModal.tsx | 5 +- .../components/DepositPaymentModal.tsx | 1 + .../payments/components/StripePaymentForm.tsx | 1 + .../components/StripePaymentModal.tsx | 5 +- .../components/StripePaymentWrapper.tsx | 4 +- .../payments/services/paymentService.ts | 8 +- .../rooms/components/ReviewSection.tsx | 1 + .../features/rooms/contexts/RoomContext.tsx | 4 +- .../features/rooms/services/roomService.ts | 4 +- .../system/components/CreateTaskModal.tsx | 1 + .../features/system/components/IconPicker.tsx | 6 +- .../system/components/TaskDetailModal.tsx | 1 + .../system/components/WorkflowBuilder.tsx | 1 + .../accountant/AnalyticsDashboardPage.tsx | 1 + .../accountant/ApprovalManagementPage.tsx | 21 +- .../src/pages/accountant/AuditTrailPage.tsx | 31 ++- .../pages/accountant/FinancialReportsPage.tsx | 1 + .../src/pages/accountant/GLManagementPage.tsx | 1 + .../accountant/InvoiceManagementPage.tsx | 1 + .../accountant/PaymentManagementPage.tsx | 1 + Frontend/src/pages/accountant/ProfilePage.tsx | 29 ++- .../pages/accountant/ReconciliationPage.tsx | 7 +- .../accountant/SecurityManagementPage.tsx | 1 + .../src/pages/admin/APIKeyManagementPage.tsx | 1 + .../src/pages/admin/AdvancedAnalyticsPage.tsx | 3 +- .../admin/AdvancedRoomManagementPage.tsx | 202 +++++++++--------- .../pages/admin/AnalyticsDashboardPage.tsx | 3 +- .../pages/admin/ApprovalManagementPage.tsx | 27 +-- .../src/pages/admin/BackupManagementPage.tsx | 24 ++- .../src/pages/admin/BannerManagementPage.tsx | 5 +- .../src/pages/admin/BlogManagementPage.tsx | 1 + .../src/pages/admin/BookingManagementPage.tsx | 17 +- Frontend/src/pages/admin/CheckOutPage.tsx | 1 + .../pages/admin/ComplaintManagementPage.tsx | 7 +- .../pages/admin/ComplianceReportingPage.tsx | 4 +- .../src/pages/admin/CookieSettingsPage.tsx | 1 + .../src/pages/admin/CurrencySettingsPage.tsx | 1 + Frontend/src/pages/admin/EditRoomPage.tsx | 5 +- .../admin/EmailCampaignManagementPage.tsx | 63 +++++- .../pages/admin/FinancialAuditTrailPage.tsx | 7 +- .../src/pages/admin/GDPRManagementPage.tsx | 2 + .../admin/GroupBookingManagementPage.tsx | 1 + Frontend/src/pages/admin/GuestProfilePage.tsx | 3 +- .../pages/admin/InspectionManagementPage.tsx | 20 +- .../pages/admin/InventoryManagementPage.tsx | 1 + Frontend/src/pages/admin/InvoiceEditPage.tsx | 1 + .../src/pages/admin/InvoiceManagementPage.tsx | 1 + .../src/pages/admin/LoyaltyManagementPage.tsx | 1 + .../pages/admin/MaintenanceManagementPage.tsx | 26 ++- .../src/pages/admin/PackageManagementPage.tsx | 1 + .../src/pages/admin/PaymentManagementPage.tsx | 9 +- Frontend/src/pages/admin/ProfilePage.tsx | 26 ++- .../pages/admin/PromotionManagementPage.tsx | 2 +- .../pages/admin/PromotionsManagementPage.tsx | 5 +- .../pages/admin/RatePlanManagementPage.tsx | 1 + .../pages/admin/ReceptionDashboardPage.tsx | 9 +- .../src/pages/admin/ReviewManagementPage.tsx | 1 + .../pages/admin/SecurityManagementPage.tsx | 35 ++- .../src/pages/admin/ServiceManagementPage.tsx | 9 +- Frontend/src/pages/admin/SettingsPage.tsx | 1 + .../pages/admin/StaffShiftDashboardPage.tsx | 3 +- .../src/pages/admin/StripeSettingsPage.tsx | 1 + .../src/pages/admin/TaskManagementPage.tsx | 6 +- .../src/pages/admin/UserManagementPage.tsx | 36 ++-- .../src/pages/admin/WebhookManagementPage.tsx | 1 + .../pages/admin/WorkflowManagementPage.tsx | 1 + Frontend/src/pages/admin/index.ts | 2 +- .../src/pages/customer/BookingDetailPage.tsx | 27 ++- .../src/pages/customer/BookingSuccessPage.tsx | 14 +- .../src/pages/customer/BoricaReturnPage.tsx | 1 + Frontend/src/pages/customer/ComplaintPage.tsx | 7 +- Frontend/src/pages/customer/DashboardPage.tsx | 6 +- .../src/pages/customer/FullPaymentPage.tsx | 2 +- .../customer/GDPRDeletionConfirmPage.tsx | 1 + Frontend/src/pages/customer/GDPRPage.tsx | 1 + .../src/pages/customer/GroupBookingPage.tsx | 1 + .../src/pages/customer/GuestRequestsPage.tsx | 13 +- Frontend/src/pages/customer/InvoicePage.tsx | 22 +- Frontend/src/pages/customer/LoyaltyPage.tsx | 23 +- .../src/pages/customer/MyBookingsPage.tsx | 1 + .../customer/PaymentConfirmationPage.tsx | 1 + Frontend/src/pages/customer/ProfilePage.tsx | 30 ++- .../src/pages/customer/RoomDetailPage.tsx | 3 +- Frontend/src/pages/customer/RoomListPage.tsx | 11 +- .../src/pages/customer/SearchResultsPage.tsx | 1 + .../pages/customer/SessionManagementPage.tsx | 6 +- .../src/pages/housekeeping/DashboardPage.tsx | 7 +- .../src/pages/housekeeping/ProfilePage.tsx | 18 +- .../src/pages/housekeeping/ShiftViewPage.tsx | 1 + Frontend/src/pages/housekeeping/TasksPage.tsx | 2 +- .../staff/AdvancedRoomManagementPage.tsx | 8 +- .../pages/staff/AnalyticsDashboardPage.tsx | 2 + .../src/pages/staff/BookingManagementPage.tsx | 13 +- .../src/pages/staff/ChatManagementPage.tsx | 1 + .../pages/staff/GuestCommunicationPage.tsx | 25 ++- Frontend/src/pages/staff/GuestProfilePage.tsx | 3 +- .../staff/GuestRequestManagementPage.tsx | 1 + .../staff/IncidentComplaintManagementPage.tsx | 11 +- .../src/pages/staff/InventoryViewPage.tsx | 7 +- .../src/pages/staff/LoyaltyManagementPage.tsx | 1 + .../src/pages/staff/PaymentManagementPage.tsx | 1 + Frontend/src/pages/staff/ProfilePage.tsx | 30 ++- .../pages/staff/ReceptionDashboardPage.tsx | 19 +- Frontend/src/pages/staff/ShiftViewPage.tsx | 1 + .../src/pages/staff/UpsellManagementPage.tsx | 20 +- .../src/shared/components/AnalyticsLoader.tsx | 15 +- .../src/shared/components/ExportButton.tsx | 2 +- Frontend/src/shared/components/Footer.tsx | 1 + Frontend/src/shared/services/index.ts | 2 +- Frontend/src/shared/utils/errorReporter.ts | 1 - Frontend/src/shared/utils/logger.ts | 2 +- 129 files changed, 785 insertions(+), 402 deletions(-) diff --git a/Frontend/src/features/content/pages/CancellationPolicyPage.tsx b/Frontend/src/features/content/pages/CancellationPolicyPage.tsx index 184e47e2..e99228ec 100644 --- a/Frontend/src/features/content/pages/CancellationPolicyPage.tsx +++ b/Frontend/src/features/content/pages/CancellationPolicyPage.tsx @@ -43,7 +43,7 @@ const CancellationPolicyPage: React.FC = () => { allElements.forEach((el) => { const htmlEl = el as HTMLElement; const tagName = htmlEl.tagName.toLowerCase(); - const _currentColor = htmlEl.style.color; + // const _currentColor = htmlEl.style.color; // Unused variable // Override inline colors to use theme-aware colors if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tagName)) { diff --git a/Frontend/src/features/content/pages/ContactPage.tsx b/Frontend/src/features/content/pages/ContactPage.tsx index 2ff54943..1f61df98 100644 --- a/Frontend/src/features/content/pages/ContactPage.tsx +++ b/Frontend/src/features/content/pages/ContactPage.tsx @@ -19,7 +19,7 @@ import { getThemeBackgroundClasses, getThemeHeroBackgroundClasses, getThemeTextC const getIconComponent = (iconName?: string, fallback: React.ComponentType<{ className?: string }> = Mail) => { if (!iconName) return fallback; - const icons = LucideIcons as Record | undefined>; + const icons = LucideIcons as unknown as Record | undefined>; // Try direct match first (for PascalCase names) if (icons[iconName]) { diff --git a/Frontend/src/features/content/pages/PrivacyPolicyPage.tsx b/Frontend/src/features/content/pages/PrivacyPolicyPage.tsx index 9b69f906..e500b743 100644 --- a/Frontend/src/features/content/pages/PrivacyPolicyPage.tsx +++ b/Frontend/src/features/content/pages/PrivacyPolicyPage.tsx @@ -8,6 +8,7 @@ import { useTheme } from '../../../shared/contexts/ThemeContext'; import Loading from '../../../shared/components/Loading'; import { createSanitizedHtml, sanitizeHtml } from '../../../shared/utils/htmlSanitizer'; import { getThemeBackgroundClasses, getThemeTextClasses, getThemeCardClasses } from '../../../shared/utils/themeUtils'; +import { isAxiosError } from 'axios'; const PrivacyPolicyPage: React.FC = () => { const { settings } = useCompanySettings(); @@ -80,7 +81,7 @@ const PrivacyPolicyPage: React.FC = () => { } catch (err: unknown) { console.error('Error fetching page content:', err); // If page is disabled (404), set pageContent to null to show disabled message - if (getUserFriendlyError(err) === 404) { + if (isAxiosError(err) && err.response?.status === 404) { setPageContent(null); } } finally { diff --git a/Frontend/src/features/content/pages/RefundsPolicyPage.tsx b/Frontend/src/features/content/pages/RefundsPolicyPage.tsx index 9a2306a5..787ffd65 100644 --- a/Frontend/src/features/content/pages/RefundsPolicyPage.tsx +++ b/Frontend/src/features/content/pages/RefundsPolicyPage.tsx @@ -8,6 +8,7 @@ import { useTheme } from '../../../shared/contexts/ThemeContext'; import Loading from '../../../shared/components/Loading'; import { createSanitizedHtml, sanitizeHtml } from '../../../shared/utils/htmlSanitizer'; import { getThemeBackgroundClasses, getThemeTextClasses, getThemeCardClasses } from '../../../shared/utils/themeUtils'; +import { isAxiosError } from 'axios'; const RefundsPolicyPage: React.FC = () => { const { settings } = useCompanySettings(); @@ -80,7 +81,7 @@ const RefundsPolicyPage: React.FC = () => { } catch (err: unknown) { console.error('Error fetching page content:', err); // If page is disabled (404), set pageContent to null to show disabled message - if (getUserFriendlyError(err) === 404) { + if (isAxiosError(err) && err.response?.status === 404) { setPageContent(null); } } finally { diff --git a/Frontend/src/features/content/pages/ServiceDetailPage.tsx b/Frontend/src/features/content/pages/ServiceDetailPage.tsx index d4052739..a030f25d 100644 --- a/Frontend/src/features/content/pages/ServiceDetailPage.tsx +++ b/Frontend/src/features/content/pages/ServiceDetailPage.tsx @@ -3,14 +3,14 @@ import { useParams, Link, useNavigate } from 'react-router-dom'; import { ArrowLeft, Share2, Tag, Star } from 'lucide-react'; import * as LucideIcons from 'lucide-react'; import pageContentService, { PageContent } from '../services/pageContentService'; -import serviceService from '../../hotel_services/services/serviceService'; +import serviceService, { Service } from '../../hotel_services/services/serviceService'; import Loading from '../../../shared/components/Loading'; import { createSanitizedHtml } from '../../../shared/utils/htmlSanitizer'; import { useFormatCurrency } from '../../payments/hooks/useFormatCurrency'; import { useTheme } from '../../../shared/contexts/ThemeContext'; import { getThemeBackgroundClasses, getThemeTextClasses, getThemeCardClasses } from '../../../shared/utils/themeUtils'; -interface ServiceSection { +export interface ServiceSection { type: 'hero' | 'text' | 'image' | 'gallery' | 'quote' | 'features' | 'cta' | 'video'; title?: string; content?: string; @@ -60,6 +60,14 @@ const ServiceDetailPage: React.FC = () => { const textClasses = getThemeTextClasses(theme.theme_layout_mode); const cardClasses = getThemeCardClasses(theme.theme_layout_mode); + // Helper function to get icon component from icon name + const getIconComponent = (iconName?: string, fallback: React.ComponentType<{ className?: string }> = Star) => { + if (!iconName) return fallback; + const icons = LucideIcons as unknown as Record | undefined>; + const IconComponent = icons[iconName] || fallback; + return IconComponent; + }; + useEffect(() => { if (slug) { fetchService(); @@ -216,7 +224,7 @@ const ServiceDetailPage: React.FC = () => { }); if (servicesResponse.success && servicesResponse.data?.services) { - servicesResponse.data.services.forEach((service: { slug?: string; category?: string; id?: number | string }) => { + servicesResponse.data.services.forEach((service: Service) => { // Skip current service if (currentService.type === 'hotel' && service.id === currentService.id) { return; @@ -229,7 +237,7 @@ const ServiceDetailPage: React.FC = () => { const serviceSlug = service.slug || service.name.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, ''); services.push({ - id: service.id, + id: service.id ?? `service-${Date.now()}`, title: service.name, slug: serviceSlug, description: service.description, @@ -243,17 +251,17 @@ const ServiceDetailPage: React.FC = () => { // Add luxury services as fallback if (pageContent?.luxury_services && Array.isArray(pageContent.luxury_services)) { - pageContent.luxury_services.forEach((s: { slug?: string; category?: string }, index: number) => { + pageContent.luxury_services.forEach((s: { slug?: string; category?: string; title?: string; name?: string; description?: string; image?: string; icon?: string }, index: number) => { if (s.slug && s.slug !== currentService.slug) { if (!currentService.category || s.category === currentService.category) { // Check if already in services (by slug) - const serviceSlug = s.slug || s.title?.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, ''); + const serviceSlug = s.slug || (s.title || s.name || 'service').toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, ''); const exists = services.some(serv => serv.slug === serviceSlug); if (!exists) { services.push({ id: `luxury-${index}`, - title: s.title || 'Service', - slug: s.slug, + title: s.title || s.name || 'Service', + slug: s.slug || serviceSlug, description: s.description, image: s.image, icon: s.icon, @@ -412,10 +420,10 @@ const ServiceDetailPage: React.FC = () => { )}
- {service.icon && (LucideIcons as Record>)[service.icon] && ( + {service.icon && (
- {React.createElement((LucideIcons as Record>)[service.icon], { + {React.createElement(getIconComponent(service.icon), { className: 'w-20 h-20 sm:w-24 sm:h-24 text-[var(--luxury-gold)] relative z-10 drop-shadow-2xl' })}
@@ -604,9 +612,7 @@ const ServiceDetailPage: React.FC = () => { )}
{section.features.map((feature, featIndex) => { - const IconComponent = feature.icon && (LucideIcons as Record>)[feature.icon] - ? (LucideIcons as Record>)[feature.icon] - : null; + const IconComponent = feature.icon ? getIconComponent(feature.icon) : null; return (
diff --git a/Frontend/src/features/content/pages/ServicesPage.tsx b/Frontend/src/features/content/pages/ServicesPage.tsx index 49db5ddf..e66a5804 100644 --- a/Frontend/src/features/content/pages/ServicesPage.tsx +++ b/Frontend/src/features/content/pages/ServicesPage.tsx @@ -13,15 +13,17 @@ import { getThemeBackgroundClasses, getThemeHeroBackgroundClasses, getThemeTextC const getIconComponent = (iconName?: string, fallback: React.ComponentType<{ className?: string }> = Award) => { if (!iconName) return fallback; + const icons = LucideIcons as unknown as Record | undefined>; + // Try direct match first (for PascalCase names) - if ((LucideIcons as Record>)[iconName]) { - return (LucideIcons as Record>)[iconName]; + if (icons[iconName]) { + return icons[iconName]; } // Convert to PascalCase (capitalize first letter) const pascalCaseName = iconName.charAt(0).toUpperCase() + iconName.slice(1).toLowerCase(); - if ((LucideIcons as Record>)[pascalCaseName]) { - return (LucideIcons as Record>)[pascalCaseName]; + if (icons[pascalCaseName]) { + return icons[pascalCaseName]; } return fallback; @@ -67,7 +69,7 @@ const ServicesPage: React.FC = () => { // Extract categories from luxury services const categories = new Set(); if (Array.isArray(content.luxury_services)) { - content.luxury_services.forEach((service: { icon?: string; name?: string; description?: string }) => { + content.luxury_services.forEach((service: { icon?: string; name?: string; description?: string; category?: string; title?: string }) => { if (service.category) { categories.add(service.category); } @@ -133,9 +135,9 @@ const ServicesPage: React.FC = () => { // Add luxury services from page content (only if not already in hotel services) if (pageContent?.luxury_services && Array.isArray(pageContent.luxury_services)) { - pageContent.luxury_services.forEach((service: { icon?: string; name?: string; description?: string }, index: number) => { + pageContent.luxury_services.forEach((service: { icon?: string; name?: string; description?: string; category?: string; title?: string; slug?: string; image?: string }, index: number) => { // Check if this service already exists in hotel services by slug - const existingSlug = service.slug || service.title?.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, ''); + const existingSlug = service.slug || (service.title || service.name || 'service').toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, ''); const existsInHotel = hotelServices.some((hs: Service) => { const hotelSlug = hs.slug || hs.name.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, ''); return hotelSlug === existingSlug; @@ -145,13 +147,13 @@ const ServicesPage: React.FC = () => { if (!existsInHotel) { services.push({ id: `luxury-${index}`, - title: service.title || 'Service', + title: service.title || service.name || 'Service', description: service.description || '', image: service.image, icon: service.icon, category: service.category, type: 'luxury', - slug: service.slug, + slug: service.slug || existingSlug, }); } }); @@ -351,10 +353,10 @@ const ServicesPage: React.FC = () => {
) : (
- {service.icon && (LucideIcons as Record>)[service.icon] ? ( + {service.icon ? (
- {React.createElement((LucideIcons as Record>)[service.icon], { + {React.createElement(getIconComponent(service.icon), { className: 'w-16 h-16 sm:w-20 sm:h-20 text-[var(--luxury-gold)] relative z-10 drop-shadow-lg' })}
diff --git a/Frontend/src/features/content/pages/TermsPage.tsx b/Frontend/src/features/content/pages/TermsPage.tsx index 75c74e75..c45de206 100644 --- a/Frontend/src/features/content/pages/TermsPage.tsx +++ b/Frontend/src/features/content/pages/TermsPage.tsx @@ -8,6 +8,7 @@ import { useTheme } from '../../../shared/contexts/ThemeContext'; import Loading from '../../../shared/components/Loading'; import { createSanitizedHtml, sanitizeHtml } from '../../../shared/utils/htmlSanitizer'; import { getThemeBackgroundClasses, getThemeTextClasses, getThemeCardClasses } from '../../../shared/utils/themeUtils'; +import { isAxiosError } from 'axios'; const TermsPage: React.FC = () => { const { settings } = useCompanySettings(); @@ -80,7 +81,7 @@ const TermsPage: React.FC = () => { } catch (err: unknown) { console.error('Error fetching page content:', err); // If page is disabled (404), set pageContent to null to show disabled message - if (getUserFriendlyError(err) === 404) { + if (isAxiosError(err) && err.response?.status === 404) { setPageContent(null); } } finally { diff --git a/Frontend/src/features/hotel_services/components/CreateBookingModal.tsx b/Frontend/src/features/hotel_services/components/CreateBookingModal.tsx index 9f61cef0..2a8e2520 100644 --- a/Frontend/src/features/hotel_services/components/CreateBookingModal.tsx +++ b/Frontend/src/features/hotel_services/components/CreateBookingModal.tsx @@ -9,6 +9,7 @@ import { toast } from 'react-toastify'; import { useFormatCurrency } from '../../payments/hooks/useFormatCurrency'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; +import { getUserFriendlyError } from '../../../shared/utils/errorSanitizer'; interface CreateBookingModalProps { isOpen: boolean; @@ -170,7 +171,7 @@ const CreateBookingModal: React.FC = ({ handleClose(); onSuccess(); } catch (error: unknown) { - toast.error(getUserFriendlyError(error) || getUserFriendlyError(error) || 'Failed to create booking'); + toast.error(getUserFriendlyError(error) || 'Failed to create booking'); } finally { setLoading(false); } diff --git a/Frontend/src/features/hotel_services/components/CreateGroupBookingModal.tsx b/Frontend/src/features/hotel_services/components/CreateGroupBookingModal.tsx index 3b4021a6..cdf0eed5 100644 --- a/Frontend/src/features/hotel_services/components/CreateGroupBookingModal.tsx +++ b/Frontend/src/features/hotel_services/components/CreateGroupBookingModal.tsx @@ -5,6 +5,7 @@ import roomService, { Room } from '../../rooms/services/roomService'; import { toast } from 'react-toastify'; import { useFormatCurrency } from '../../payments/hooks/useFormatCurrency'; import DatePicker from 'react-datepicker'; +import { getUserFriendlyError } from '../../../shared/utils/errorSanitizer'; import 'react-datepicker/dist/react-datepicker.css'; interface RoomBlock { diff --git a/Frontend/src/features/hotel_services/components/HousekeepingManagement.tsx b/Frontend/src/features/hotel_services/components/HousekeepingManagement.tsx index f624e66a..53ddbdc6 100644 --- a/Frontend/src/features/hotel_services/components/HousekeepingManagement.tsx +++ b/Frontend/src/features/hotel_services/components/HousekeepingManagement.tsx @@ -18,6 +18,7 @@ import roomService, { Room } from '../../rooms/services/roomService'; import userService, { User as UserType } from '../../auth/services/userService'; import useAuthStore from '../../../store/useAuthStore'; import DatePicker from 'react-datepicker'; +import { getUserFriendlyError } from '../../../shared/utils/errorSanitizer'; import 'react-datepicker/dist/react-datepicker.css'; const HousekeepingManagement: React.FC = () => { @@ -82,7 +83,15 @@ const HousekeepingManagement: React.FC = () => { const fetchTasks = async () => { try { setLoading(true); - const params: { page: number; limit: number; room_id?: number; status?: string } = { + const params: { + page: number; + limit: number; + room_id?: number; + status?: string; + task_type?: string; + date?: string; + include_cleaning_rooms?: boolean; + } = { page: currentPage, limit: 10, include_cleaning_rooms: true // Include rooms in cleaning status @@ -173,7 +182,7 @@ const HousekeepingManagement: React.FC = () => { const items = defaultChecklistItems[type] || []; setFormData({ ...formData, - task_type: type as 'cleaning' | 'inspection' | 'maintenance', + task_type: type as 'checkout' | 'stayover' | 'vacant' | 'inspection' | 'turndown', checklist_items: items.map(item => ({ item, completed: false, notes: '' })), }); }; diff --git a/Frontend/src/features/hotel_services/components/InspectionManagement.tsx b/Frontend/src/features/hotel_services/components/InspectionManagement.tsx index 3ae3ba6c..c87c2e64 100644 --- a/Frontend/src/features/hotel_services/components/InspectionManagement.tsx +++ b/Frontend/src/features/hotel_services/components/InspectionManagement.tsx @@ -22,6 +22,7 @@ import roomService, { Room } from '../../rooms/services/roomService'; import userService, { User as UserType } from '../../auth/services/userService'; import useAuthStore from '../../../store/useAuthStore'; import DatePicker from 'react-datepicker'; +import { getUserFriendlyError } from '../../../shared/utils/errorSanitizer'; import 'react-datepicker/dist/react-datepicker.css'; const InspectionManagement: React.FC = () => { @@ -88,7 +89,7 @@ const InspectionManagement: React.FC = () => { const fetchInspections = async () => { try { setLoading(true); - const params: { page: number; limit: number; room_id?: number; inspection_type?: string } = { page: currentPage, limit: 10 }; + const params: { page: number; limit: number; room_id?: number; inspection_type?: string; status?: string } = { page: currentPage, limit: 10 }; if (filters.room_id) params.room_id = parseInt(filters.room_id); if (filters.inspection_type) params.inspection_type = filters.inspection_type; if (filters.status) params.status = filters.status; @@ -480,7 +481,7 @@ const InspectionManagement: React.FC = () => { setFormData({ ...formData, maintenance_type: e.target.value as 'repair' | 'cleaning' | 'inspection' | 'upgrade' | 'other' })} + onChange={(e) => setFormData({ ...formData, maintenance_type: e.target.value as 'preventive' | 'corrective' | 'emergency' | 'upgrade' | 'inspection' })} className="w-full border border-gray-300 rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" > diff --git a/Frontend/src/features/loyalty/services/loyaltyService.ts b/Frontend/src/features/loyalty/services/loyaltyService.ts index f1c3c1e5..fd61eff3 100644 --- a/Frontend/src/features/loyalty/services/loyaltyService.ts +++ b/Frontend/src/features/loyalty/services/loyaltyService.ts @@ -153,7 +153,7 @@ const loyaltyService = { limit: number = 20, transactionType?: string ): Promise => { - const params: { page: number; limit: number } = { page, limit }; + const params: { page: number; limit: number; transaction_type?: string } = { page, limit }; if (transactionType) { params.transaction_type = transactionType; } @@ -232,7 +232,7 @@ const loyaltyService = { }; }; }> => { - const params: { page: number; limit: number } = { page, limit }; + const params: { page: number; limit: number; search?: string; tier_id?: number } = { page, limit }; if (search) params.search = search; if (tierId) params.tier_id = tierId; const response = await apiClient.get('/api/loyalty/admin/users', { params }); diff --git a/Frontend/src/features/notifications/components/ChatWidget.tsx b/Frontend/src/features/notifications/components/ChatWidget.tsx index a2a66ba2..0d31795f 100644 --- a/Frontend/src/features/notifications/components/ChatWidget.tsx +++ b/Frontend/src/features/notifications/components/ChatWidget.tsx @@ -7,6 +7,7 @@ import { useCompanySettings } from '../../../shared/contexts/CompanySettingsCont import { toast } from 'react-toastify'; import ConfirmationDialog from '../../../shared/components/ConfirmationDialog'; import { formatWorkingHours } from '../../../shared/utils/format'; +import { getUserFriendlyError } from '../../../shared/utils/errorSanitizer'; interface ChatWidgetProps { onClose?: () => void; diff --git a/Frontend/src/features/notifications/components/InAppNotificationBell.tsx b/Frontend/src/features/notifications/components/InAppNotificationBell.tsx index 33735269..41a39e04 100644 --- a/Frontend/src/features/notifications/components/InAppNotificationBell.tsx +++ b/Frontend/src/features/notifications/components/InAppNotificationBell.tsx @@ -4,6 +4,7 @@ import { toast } from 'react-toastify'; import notificationService, { Notification } from '../services/notificationService'; import { formatDate } from '../../../shared/utils/format'; import useAuthStore from '../../../store/useAuthStore'; +import { getUserFriendlyError } from '../../../shared/utils/errorSanitizer'; const InAppNotificationBell: React.FC = () => { const { isAuthenticated, token, isLoading } = useAuthStore(); @@ -108,7 +109,7 @@ const InAppNotificationBell: React.FC = () => { try { await notificationService.markAsRead(notificationId); setNotifications(notifications.map(n => - n.id === notificationId ? { ...n, status: 'read' as 'unread' | 'read', read_at: new Date().toISOString() } : n + n.id === notificationId ? { ...n, status: 'read' as Notification['status'], read_at: new Date().toISOString() } : n )); setUnreadCount(Math.max(0, unreadCount - 1)); } catch (error: unknown) { @@ -121,7 +122,7 @@ const InAppNotificationBell: React.FC = () => { setLoading(true); const unread = notifications.filter(n => !n.read_at); await Promise.all(unread.map(n => notificationService.markAsRead(n.id))); - setNotifications(notifications.map(n => ({ ...n, status: 'read' as 'read' | 'unread', read_at: new Date().toISOString() }))); + setNotifications(notifications.map(n => ({ ...n, status: 'read' as Notification['status'], read_at: new Date().toISOString() }))); setUnreadCount(0); } catch (error: unknown) { toast.error(getUserFriendlyError(error) || 'Failed to mark all as read'); diff --git a/Frontend/src/features/notifications/components/NotificationPreferences.tsx b/Frontend/src/features/notifications/components/NotificationPreferences.tsx index 3bd3fdfe..17b6014a 100644 --- a/Frontend/src/features/notifications/components/NotificationPreferences.tsx +++ b/Frontend/src/features/notifications/components/NotificationPreferences.tsx @@ -3,6 +3,7 @@ import { Bell, Mail, MessageSquare, Smartphone, Save } from 'lucide-react'; import { toast } from 'react-toastify'; import Loading from '../../../shared/components/Loading'; import notificationService, { NotificationPreferences as NotificationPreferencesType } from '../services/notificationService'; +import { getUserFriendlyError } from '../../../shared/utils/errorSanitizer'; const NotificationPreferences: React.FC = () => { const [preferences, setPreferences] = useState(null); diff --git a/Frontend/src/features/notifications/components/NotificationTemplatesModal.tsx b/Frontend/src/features/notifications/components/NotificationTemplatesModal.tsx index 2f592e9b..60082eca 100644 --- a/Frontend/src/features/notifications/components/NotificationTemplatesModal.tsx +++ b/Frontend/src/features/notifications/components/NotificationTemplatesModal.tsx @@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'; import { X, Plus } from 'lucide-react'; import { toast } from 'react-toastify'; import notificationService, { NotificationTemplate } from '../services/notificationService'; +import { getUserFriendlyError } from '../../../shared/utils/errorSanitizer'; interface NotificationTemplatesModalProps { onClose: () => void; diff --git a/Frontend/src/features/notifications/components/SendNotificationModal.tsx b/Frontend/src/features/notifications/components/SendNotificationModal.tsx index 7c995996..331bc133 100644 --- a/Frontend/src/features/notifications/components/SendNotificationModal.tsx +++ b/Frontend/src/features/notifications/components/SendNotificationModal.tsx @@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'; import { X } from 'lucide-react'; import { toast } from 'react-toastify'; import notificationService from '../services/notificationService'; +import { getUserFriendlyError } from '../../../shared/utils/errorSanitizer'; interface SendNotificationModalProps { onClose: () => void; diff --git a/Frontend/src/features/payments/components/BoricaPaymentModal.tsx b/Frontend/src/features/payments/components/BoricaPaymentModal.tsx index e8ed59f4..6ebcdff6 100644 --- a/Frontend/src/features/payments/components/BoricaPaymentModal.tsx +++ b/Frontend/src/features/payments/components/BoricaPaymentModal.tsx @@ -3,6 +3,7 @@ import { createBoricaPayment } from '../services/paymentService'; import { X, Loader2, AlertCircle, CreditCard } from 'lucide-react'; import { toast } from 'react-toastify'; import { useFormatCurrency } from '../hooks/useFormatCurrency'; +import { getUserFriendlyError } from '../../../shared/utils/errorSanitizer'; interface BoricaPaymentModalProps { isOpen: boolean; @@ -75,7 +76,7 @@ const BoricaPaymentModal: React.FC = ({ // Create a form and submit it to Borica gateway const form = document.createElement('form'); form.method = 'POST'; - form.action = paymentRequest.gateway_url; + form.action = (paymentRequest.gateway_url as string) || ''; form.style.display = 'none'; // Add all form fields @@ -172,7 +173,7 @@ const BoricaPaymentModal: React.FC = ({
Order ID: - {paymentRequest.order_id} + {String(paymentRequest.order_id || '')}
Amount: diff --git a/Frontend/src/features/payments/components/DepositPaymentModal.tsx b/Frontend/src/features/payments/components/DepositPaymentModal.tsx index 7236050d..ab93149b 100644 --- a/Frontend/src/features/payments/components/DepositPaymentModal.tsx +++ b/Frontend/src/features/payments/components/DepositPaymentModal.tsx @@ -17,6 +17,7 @@ import { import { useFormatCurrency } from '../hooks/useFormatCurrency'; import StripePaymentModal from './StripePaymentModal'; import PayPalPaymentModal from './PayPalPaymentModal'; +import { getUserFriendlyError } from '../../../shared/utils/errorSanitizer'; interface DepositPaymentModalProps { isOpen: boolean; diff --git a/Frontend/src/features/payments/components/StripePaymentForm.tsx b/Frontend/src/features/payments/components/StripePaymentForm.tsx index 9778c457..e375990d 100644 --- a/Frontend/src/features/payments/components/StripePaymentForm.tsx +++ b/Frontend/src/features/payments/components/StripePaymentForm.tsx @@ -6,6 +6,7 @@ import { } from '@stripe/react-stripe-js'; import { CreditCard, Loader2, AlertCircle, CheckCircle } from 'lucide-react'; import { toast } from 'react-toastify'; +import { getUserFriendlyError } from '../../../shared/utils/errorSanitizer'; interface StripePaymentFormProps { clientSecret: string; diff --git a/Frontend/src/features/payments/components/StripePaymentModal.tsx b/Frontend/src/features/payments/components/StripePaymentModal.tsx index 19182bc1..90c9ddc9 100644 --- a/Frontend/src/features/payments/components/StripePaymentModal.tsx +++ b/Frontend/src/features/payments/components/StripePaymentModal.tsx @@ -1,10 +1,11 @@ import React, { useState, useEffect } from 'react'; -import { loadStripe, StripeElementsOptions } from '@stripe/stripe-js'; +import { loadStripe, StripeElementsOptions, Stripe } from '@stripe/stripe-js'; import { Elements } from '@stripe/react-stripe-js'; import StripePaymentForm from './StripePaymentForm'; import { createStripePaymentIntent, confirmStripePayment } from '../services/paymentService'; import { X, Loader2, AlertCircle } from 'lucide-react'; import { toast } from 'react-toastify'; +import { getUserFriendlyError } from '../../../shared/utils/errorSanitizer'; interface StripePaymentModalProps { isOpen: boolean; @@ -23,7 +24,7 @@ const StripePaymentModal: React.FC = ({ onSuccess, onClose, }) => { - const [stripePromise, setStripePromise] = useState | null>(null); + const [stripePromise, setStripePromise] = useState | null>(null); const [clientSecret, setClientSecret] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); diff --git a/Frontend/src/features/payments/components/StripePaymentWrapper.tsx b/Frontend/src/features/payments/components/StripePaymentWrapper.tsx index a37185b2..c1274c26 100644 --- a/Frontend/src/features/payments/components/StripePaymentWrapper.tsx +++ b/Frontend/src/features/payments/components/StripePaymentWrapper.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { loadStripe, StripeElementsOptions } from '@stripe/stripe-js'; +import { loadStripe, StripeElementsOptions, Stripe } from '@stripe/stripe-js'; import { Elements } from '@stripe/react-stripe-js'; import StripePaymentForm from './StripePaymentForm'; import { createStripePaymentIntent, confirmStripePayment } from '../services/paymentService'; @@ -20,7 +20,7 @@ const StripePaymentWrapper: React.FC = ({ onSuccess, onError, }) => { - const [stripePromise, setStripePromise] = useState | null>(null); + const [stripePromise, setStripePromise] = useState | null>(null); const [clientSecret, setClientSecret] = useState(null); const [, setPublishableKey] = useState(null); const [loading, setLoading] = useState(true); diff --git a/Frontend/src/features/payments/services/paymentService.ts b/Frontend/src/features/payments/services/paymentService.ts index b681b74d..a5405fe0 100644 --- a/Frontend/src/features/payments/services/paymentService.ts +++ b/Frontend/src/features/payments/services/paymentService.ts @@ -61,7 +61,7 @@ export const createPayment = async ( // Handle both 'status: success' and 'success: true' formats return { success: data.status === 'success' || data.success === true, - data: data.data || {}, + data: (data.data as { payment: Payment }) || { payment: {} as Payment }, message: data.message, }; }; @@ -76,7 +76,7 @@ export const getPaymentByBookingId = async ( // Handle both 'status: success' and 'success: true' formats return { success: data.status === 'success' || data.success === true, - data: data.data || {}, + data: (data.data as { payment: Payment }) || { payment: {} as Payment }, message: data.message, }; }; @@ -125,7 +125,7 @@ export const getBankTransferInfo = async ( // Handle both 'status: success' and 'success: true' formats return { success: data.status === 'success' || data.success === true, - data: data.data || {}, + data: (data.data as { payment: Payment; bank_info: BankInfo }) || { payment: {} as Payment, bank_info: {} as BankInfo }, message: data.message, }; }; @@ -149,7 +149,7 @@ export const confirmDepositPayment = async ( // Handle both 'status: success' and 'success: true' formats return { success: data.status === 'success' || data.success === true, - data: data.data || {}, + data: (data.data as { payment: Payment; booking: Booking }) || { payment: {} as Payment, booking: {} as Booking }, message: data.message, }; }; diff --git a/Frontend/src/features/rooms/components/ReviewSection.tsx b/Frontend/src/features/rooms/components/ReviewSection.tsx index 3f182047..4ae2fa1b 100644 --- a/Frontend/src/features/rooms/components/ReviewSection.tsx +++ b/Frontend/src/features/rooms/components/ReviewSection.tsx @@ -17,6 +17,7 @@ import { useAntibotForm } from '../../auth/hooks/useAntibotForm'; import HoneypotField from '../../../shared/components/HoneypotField'; import { useTheme } from '../../../shared/contexts/ThemeContext'; import { getThemeTextClasses, getThemeCardClasses, getThemeInputClasses } from '../../../shared/utils/themeUtils'; +import { getUserFriendlyError } from '../../../shared/utils/errorSanitizer'; interface ReviewSectionProps { roomId: number; diff --git a/Frontend/src/features/rooms/contexts/RoomContext.tsx b/Frontend/src/features/rooms/contexts/RoomContext.tsx index 06b5b8b7..2388d216 100644 --- a/Frontend/src/features/rooms/contexts/RoomContext.tsx +++ b/Frontend/src/features/rooms/contexts/RoomContext.tsx @@ -3,6 +3,8 @@ import roomService, { Room } from '../services/roomService'; import advancedRoomService, { RoomStatusBoardItem } from '../services/advancedRoomService'; import { toast } from 'react-toastify'; import { logger } from '../../../shared/utils/logger'; +import { getUserFriendlyError } from '../../../shared/utils/errorSanitizer'; +import { isAxiosError } from 'axios'; interface RoomContextType { // Room list state @@ -175,7 +177,7 @@ export const RoomProvider: React.FC = ({ children }) => { } // Handle 401 Unauthorized gracefully - user may not have admin/staff role - if (getUserFriendlyError(error) === 401) { + if (isAxiosError(error) && error.response?.status === 401) { setStatusBoardError(null); // Don't set error for unauthorized access setStatusBoardRooms([]); // Clear status board if unauthorized return; // Silently return without logging diff --git a/Frontend/src/features/rooms/services/roomService.ts b/Frontend/src/features/rooms/services/roomService.ts index cb76261c..74b51626 100644 --- a/Frontend/src/features/rooms/services/roomService.ts +++ b/Frontend/src/features/rooms/services/roomService.ts @@ -5,7 +5,7 @@ export interface Room { room_type_id: number; room_number: string; floor: number; - status: 'available' | 'occupied' | 'maintenance'; + status: 'available' | 'occupied' | 'maintenance' | 'cleaning' | 'reserved'; featured: boolean; price?: number; description?: string; @@ -295,7 +295,7 @@ export interface CreateRoomData { room_number: string; floor: number; room_type_id: number; - status: 'available' | 'occupied' | 'maintenance'; + status: 'available' | 'occupied' | 'maintenance' | 'cleaning' | 'reserved'; featured?: boolean; price?: number; description?: string; diff --git a/Frontend/src/features/system/components/CreateTaskModal.tsx b/Frontend/src/features/system/components/CreateTaskModal.tsx index 83c12be2..35919678 100644 --- a/Frontend/src/features/system/components/CreateTaskModal.tsx +++ b/Frontend/src/features/system/components/CreateTaskModal.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import { X } from 'lucide-react'; import { toast } from 'react-toastify'; import taskService from '../services/taskService'; +import { getUserFriendlyError } from '../../../shared/utils/errorSanitizer'; interface CreateTaskModalProps { onClose: () => void; diff --git a/Frontend/src/features/system/components/IconPicker.tsx b/Frontend/src/features/system/components/IconPicker.tsx index 0e146a8c..377980ee 100644 --- a/Frontend/src/features/system/components/IconPicker.tsx +++ b/Frontend/src/features/system/components/IconPicker.tsx @@ -91,7 +91,7 @@ const IconPicker: React.FC = ({ value, onChange, label = 'Icon' continue; } - const iconComponent = (LucideIcons as Record>)[iconName]; + const iconComponent = (LucideIcons as unknown as Record>)[iconName]; // Only include if it's a function (React component) if (typeof iconComponent === 'function') { @@ -127,7 +127,7 @@ const IconPicker: React.FC = ({ value, onChange, label = 'Icon' ); }, [searchQuery, allIcons]); - const selectedIcon = normalizedValue && (LucideIcons as Record>)[normalizedValue] ? (LucideIcons as Record>)[normalizedValue] : null; + const selectedIcon = normalizedValue && (LucideIcons as unknown as Record>)[normalizedValue] ? (LucideIcons as unknown as Record>)[normalizedValue] : null; const handleIconSelect = (iconName: string) => { onChange(iconName); @@ -202,7 +202,7 @@ const IconPicker: React.FC = ({ value, onChange, label = 'Icon' )}
{filteredIcons.slice(0, searchQuery.trim() ? 500 : 300).map((iconName) => { - const IconComponent = (LucideIcons as Record>)[iconName]; + const IconComponent = (LucideIcons as unknown as Record>)[iconName]; if (!IconComponent) return null; const isSelected = normalizedValue === iconName; diff --git a/Frontend/src/features/system/components/TaskDetailModal.tsx b/Frontend/src/features/system/components/TaskDetailModal.tsx index c1719117..1254162a 100644 --- a/Frontend/src/features/system/components/TaskDetailModal.tsx +++ b/Frontend/src/features/system/components/TaskDetailModal.tsx @@ -4,6 +4,7 @@ import { toast } from 'react-toastify'; import { Task } from '../services/taskService'; import taskService from '../services/taskService'; import { formatDate } from '../../../shared/utils/format'; +import { getUserFriendlyError } from '../../../shared/utils/errorSanitizer'; interface TaskDetailModalProps { task: Task; diff --git a/Frontend/src/features/system/components/WorkflowBuilder.tsx b/Frontend/src/features/system/components/WorkflowBuilder.tsx index 9b3334e9..44b90be8 100644 --- a/Frontend/src/features/system/components/WorkflowBuilder.tsx +++ b/Frontend/src/features/system/components/WorkflowBuilder.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import { X, Plus, Trash2, GripVertical, Save } from 'lucide-react'; import { toast } from 'react-toastify'; import workflowService, { Workflow, WorkflowStep } from '../services/workflowService'; +import { getUserFriendlyError } from '../../../shared/utils/errorSanitizer'; interface WorkflowBuilderProps { workflow?: Workflow | null; diff --git a/Frontend/src/pages/accountant/AnalyticsDashboardPage.tsx b/Frontend/src/pages/accountant/AnalyticsDashboardPage.tsx index ce24f6d6..0696e114 100644 --- a/Frontend/src/pages/accountant/AnalyticsDashboardPage.tsx +++ b/Frontend/src/pages/accountant/AnalyticsDashboardPage.tsx @@ -61,6 +61,7 @@ import analyticsService, { import { SimpleBarChart, SimpleLineChart, SimplePieChart, KPICard } from '../../features/analytics/components/SimpleChart'; import { exportData } from '../../shared/utils/exportUtils'; import CustomReportBuilder from '../../features/analytics/components/CustomReportBuilder'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; type AnalyticsTab = 'overview' | 'reports' | 'revenue' | 'operational' | 'guest' | 'financial' | 'audit-logs' | 'reviews'; diff --git a/Frontend/src/pages/accountant/ApprovalManagementPage.tsx b/Frontend/src/pages/accountant/ApprovalManagementPage.tsx index 5ac1abc0..94b41871 100644 --- a/Frontend/src/pages/accountant/ApprovalManagementPage.tsx +++ b/Frontend/src/pages/accountant/ApprovalManagementPage.tsx @@ -6,6 +6,7 @@ import Loading from '../../shared/components/Loading'; import EmptyState from '../../shared/components/EmptyState'; import { formatDate } from '../../shared/utils/format'; import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const ApprovalManagementPage: React.FC = () => { const { formatCurrency } = useFormatCurrency(); @@ -23,7 +24,7 @@ const ApprovalManagementPage: React.FC = () => { const fetchApprovals = async () => { try { setLoading(true); - const params: { status?: string } = {}; + const params: { status?: ApprovalStatus } = {}; if (filter !== 'all') params.status = filter; const response = await approvalService.getApprovals(params); setApprovals(response.data || []); @@ -225,19 +226,29 @@ const ApprovalManagementPage: React.FC = () => {

{formatCurrency(selectedApproval.amount)}

)} - {selectedApproval.previous_value && ( + {selectedApproval.previous_value != null && (

Previous Value

-                    {JSON.stringify(selectedApproval.previous_value, null, 2)}
+                    {(() => {
+                      const value = selectedApproval.previous_value;
+                      return typeof value === 'string' 
+                        ? value 
+                        : JSON.stringify(value, null, 2);
+                    })()}
                   
)} - {selectedApproval.new_value && ( + {selectedApproval.new_value != null && (

New Value

-                    {JSON.stringify(selectedApproval.new_value, null, 2)}
+                    {(() => {
+                      const value = selectedApproval.new_value;
+                      return typeof value === 'string' 
+                        ? value 
+                        : JSON.stringify(value, null, 2);
+                    })()}
                   
)} diff --git a/Frontend/src/pages/accountant/AuditTrailPage.tsx b/Frontend/src/pages/accountant/AuditTrailPage.tsx index f995097d..573f4ad8 100644 --- a/Frontend/src/pages/accountant/AuditTrailPage.tsx +++ b/Frontend/src/pages/accountant/AuditTrailPage.tsx @@ -1,17 +1,18 @@ import React, { useState, useEffect } from 'react'; import { Download, Filter } from 'lucide-react'; import { toast } from 'react-toastify'; -import financialAuditService, { FinancialAuditRecord } from '../../features/payments/services/financialAuditService'; +import financialAuditService, { FinancialAuditRecord, FinancialAuditFilters } from '../../features/payments/services/financialAuditService'; import Loading from '../../shared/components/Loading'; import EmptyState from '../../shared/components/EmptyState'; import { formatDate } from '../../shared/utils/format'; import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const AuditTrailPage: React.FC = () => { const { formatCurrency } = useFormatCurrency(); const [loading, setLoading] = useState(true); const [records, setRecords] = useState([]); - const [pagination, setPagination] = useState(null); + const [pagination, setPagination] = useState<{ page: number; limit: number; total: number; total_pages: number } | null>(null); const [filters, setFilters] = useState({ action_type: '', user_id: '', @@ -33,7 +34,18 @@ const AuditTrailPage: React.FC = () => { const fetchAuditTrail = async () => { try { setLoading(true); - const response = await financialAuditService.getAuditTrail(filters); + const auditFilters: FinancialAuditFilters = { + action_type: filters.action_type || undefined, + user_id: filters.user_id ? parseInt(filters.user_id) : undefined, + start_date: filters.start_date || undefined, + end_date: filters.end_date || undefined, + payment_id: filters.payment_id ? parseInt(filters.payment_id) : undefined, + invoice_id: filters.invoice_id ? parseInt(filters.invoice_id) : undefined, + booking_id: filters.booking_id ? parseInt(filters.booking_id) : undefined, + page: filters.page, + limit: filters.limit, + }; + const response = await financialAuditService.getAuditTrail(auditFilters); if (response.status === 'success' && response.data) { setRecords(response.data.audit_trail || []); setPagination(response.data.pagination); @@ -47,7 +59,18 @@ const AuditTrailPage: React.FC = () => { const handleExport = async (format: 'csv' | 'json') => { try { - const blob = await financialAuditService.exportAuditTrail(filters, format); + const auditFilters: FinancialAuditFilters = { + action_type: filters.action_type || undefined, + user_id: filters.user_id ? parseInt(filters.user_id) : undefined, + start_date: filters.start_date || undefined, + end_date: filters.end_date || undefined, + payment_id: filters.payment_id ? parseInt(filters.payment_id) : undefined, + invoice_id: filters.invoice_id ? parseInt(filters.invoice_id) : undefined, + booking_id: filters.booking_id ? parseInt(filters.booking_id) : undefined, + page: filters.page, + limit: filters.limit, + }; + const blob = await financialAuditService.exportAuditTrail(auditFilters, format); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; diff --git a/Frontend/src/pages/accountant/FinancialReportsPage.tsx b/Frontend/src/pages/accountant/FinancialReportsPage.tsx index a66af471..d9326b02 100644 --- a/Frontend/src/pages/accountant/FinancialReportsPage.tsx +++ b/Frontend/src/pages/accountant/FinancialReportsPage.tsx @@ -6,6 +6,7 @@ import glService from '../../features/payments/services/glService'; import Loading from '../../shared/components/Loading'; import { formatDate } from '../../shared/utils/format'; import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const FinancialReportsPage: React.FC = () => { const { formatCurrency } = useFormatCurrency(); diff --git a/Frontend/src/pages/accountant/GLManagementPage.tsx b/Frontend/src/pages/accountant/GLManagementPage.tsx index 35ba6bb7..3667f263 100644 --- a/Frontend/src/pages/accountant/GLManagementPage.tsx +++ b/Frontend/src/pages/accountant/GLManagementPage.tsx @@ -6,6 +6,7 @@ import Loading from '../../shared/components/Loading'; import EmptyState from '../../shared/components/EmptyState'; import { formatDate } from '../../shared/utils/format'; import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const GLManagementPage: React.FC = () => { const { formatCurrency } = useFormatCurrency(); diff --git a/Frontend/src/pages/accountant/InvoiceManagementPage.tsx b/Frontend/src/pages/accountant/InvoiceManagementPage.tsx index e2e4f2ef..eb508dde 100644 --- a/Frontend/src/pages/accountant/InvoiceManagementPage.tsx +++ b/Frontend/src/pages/accountant/InvoiceManagementPage.tsx @@ -9,6 +9,7 @@ import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurren import { useNavigate } from 'react-router-dom'; import { formatDate } from '../../shared/utils/format'; import { logger } from '../../shared/utils/logger'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const InvoiceManagementPage: React.FC = () => { const { formatCurrency } = useFormatCurrency(); diff --git a/Frontend/src/pages/accountant/PaymentManagementPage.tsx b/Frontend/src/pages/accountant/PaymentManagementPage.tsx index a9e28499..f864fb74 100644 --- a/Frontend/src/pages/accountant/PaymentManagementPage.tsx +++ b/Frontend/src/pages/accountant/PaymentManagementPage.tsx @@ -9,6 +9,7 @@ import ExportButton from '../../shared/components/ExportButton'; import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency'; import { formatDate } from '../../shared/utils/format'; import { getPaymentStatusColor, getPaymentMethodLabel } from '../../shared/utils/paymentUtils'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const PaymentManagementPage: React.FC = () => { const { formatCurrency } = useFormatCurrency(); diff --git a/Frontend/src/pages/accountant/ProfilePage.tsx b/Frontend/src/pages/accountant/ProfilePage.tsx index 3d3c3f93..83d801bb 100644 --- a/Frontend/src/pages/accountant/ProfilePage.tsx +++ b/Frontend/src/pages/accountant/ProfilePage.tsx @@ -24,7 +24,9 @@ import { Trash2, Database, Smartphone, - Tablet + Tablet, + Eye, + EyeOff } from 'lucide-react'; import { toast } from 'react-toastify'; import authService from '../../features/auth/services/authService'; @@ -38,7 +40,8 @@ import { useGlobalLoading } from '../../shared/contexts/LoadingContext'; import { normalizeImageUrl } from '../../shared/utils/imageUtils'; import { formatDate } from '../../shared/utils/format'; import { UserSession } from '../../features/auth/services/sessionService'; -import { useNavigate } from 'react-router-dom'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; +import { isAxiosError } from 'axios'; const profileValidationSchema = yup.object().shape({ name: yup @@ -83,7 +86,7 @@ type PasswordFormData = yup.InferType; const ProfilePage: React.FC = () => { const { userInfo, setUser } = useAuthStore(); const { setLoading } = useGlobalLoading(); - const _navigate = useNavigate(); + // const _navigate = useNavigate(); // Reserved for future use const [activeTab, setActiveTab] = useState<'profile' | 'password' | 'mfa' | 'sessions' | 'gdpr'>('profile'); const [avatarPreview, setAvatarPreview] = useState(null); const [avatarError, setAvatarError] = useState(false); @@ -105,8 +108,8 @@ const ProfilePage: React.FC = () => { const [showBackupCodes, setShowBackupCodes] = useState(null); const [showMfaSecret, setShowMfaSecret] = useState(false); const mfaAbortControllerRef = useRef(null); - const _sessionsAbortControllerRef = useRef(null); - const _gdprAbortControllerRef = useRef(null); + // const _sessionsAbortControllerRef = useRef(null); // Reserved for future use + // const _gdprAbortControllerRef = useRef(null); // Reserved for future use const fetchProfile = async () => { @@ -241,8 +244,16 @@ const ProfilePage: React.FC = () => { if (response.status === 'success' || response.success) { const updatedUser = response.data?.user || response.data; - if (updatedUser) { - setUser(updatedUser); + if (updatedUser && typeof updatedUser === 'object' && 'id' in updatedUser) { + setUser({ + id: (updatedUser as { id: number }).id, + name: ((updatedUser as { name?: string; full_name?: string }).name || (updatedUser as { name?: string; full_name?: string }).full_name || '') as string, + email: (updatedUser as { email: string }).email, + phone: ((updatedUser as { phone?: string; phone_number?: string }).phone || (updatedUser as { phone?: string; phone_number?: string }).phone_number) as string | undefined, + avatar: (updatedUser as { avatar?: string }).avatar, + role: (updatedUser as { role: string }).role, + createdAt: ((updatedUser as { createdAt?: string; created_at?: string }).createdAt || (updatedUser as { createdAt?: string; created_at?: string }).created_at) as string | undefined, + }); toast.success('Profile updated successfully!'); refetchProfile(); } @@ -1158,7 +1169,7 @@ const SessionsTab: React.FC = () => { fetchSessions(); } } catch (error: unknown) { - if (getUserFriendlyError(error) === 401) { + if (isAxiosError(error) && error.response?.status === 401) { toast.warning('Your session has been revoked. You will be logged out.'); setTimeout(() => { window.location.href = '/'; @@ -1184,7 +1195,7 @@ const SessionsTab: React.FC = () => { fetchSessions(); } } catch (error: unknown) { - if (getUserFriendlyError(error) === 401) { + if (isAxiosError(error) && error.response?.status === 401) { toast.warning('All sessions have been revoked. You will be logged out.'); setTimeout(() => { window.location.href = '/'; diff --git a/Frontend/src/pages/accountant/ReconciliationPage.tsx b/Frontend/src/pages/accountant/ReconciliationPage.tsx index b7f07fd5..f29d1762 100644 --- a/Frontend/src/pages/accountant/ReconciliationPage.tsx +++ b/Frontend/src/pages/accountant/ReconciliationPage.tsx @@ -1,17 +1,18 @@ import React, { useState, useEffect } from 'react'; -import { Play } from 'lucide-react'; +import { Play, AlertTriangle } from 'lucide-react'; import { toast } from 'react-toastify'; -import reconciliationService, { ReconciliationException, ExceptionStatus } from '../../features/payments/services/reconciliationService'; +import reconciliationService, { ReconciliationException, ExceptionStatus, ExceptionStats } from '../../features/payments/services/reconciliationService'; import Loading from '../../shared/components/Loading'; import EmptyState from '../../shared/components/EmptyState'; import { formatDate } from '../../shared/utils/format'; import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const ReconciliationPage: React.FC = () => { const { formatCurrency } = useFormatCurrency(); const [loading, setLoading] = useState(false); const [exceptions, setExceptions] = useState([]); - const [stats, setStats] = useState | null>(null); + const [stats, setStats] = useState(null); const [selectedException, setSelectedException] = useState(null); const [filter, setFilter] = useState('all'); const [comment, setComment] = useState(''); diff --git a/Frontend/src/pages/accountant/SecurityManagementPage.tsx b/Frontend/src/pages/accountant/SecurityManagementPage.tsx index d983f5fa..ef19709b 100644 --- a/Frontend/src/pages/accountant/SecurityManagementPage.tsx +++ b/Frontend/src/pages/accountant/SecurityManagementPage.tsx @@ -6,6 +6,7 @@ import accountantSecurityService, { AccountantSession, AccountantActivityLog, MF import Loading from '../../shared/components/Loading'; import EmptyState from '../../shared/components/EmptyState'; import { formatDate } from '../../shared/utils/format'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const SecurityManagementPage: React.FC = () => { const [searchParams] = useSearchParams(); diff --git a/Frontend/src/pages/admin/APIKeyManagementPage.tsx b/Frontend/src/pages/admin/APIKeyManagementPage.tsx index 5438db09..b10b4a2a 100644 --- a/Frontend/src/pages/admin/APIKeyManagementPage.tsx +++ b/Frontend/src/pages/admin/APIKeyManagementPage.tsx @@ -4,6 +4,7 @@ import apiKeyService, { APIKey, CreateAPIKeyData, UpdateAPIKeyData } from '../.. import { toast } from 'react-toastify'; import Loading from '../../shared/components/Loading'; import { formatDate } from '../../shared/utils/format'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const APIKeyManagementPage: React.FC = () => { const [apiKeys, setApiKeys] = useState([]); diff --git a/Frontend/src/pages/admin/AdvancedAnalyticsPage.tsx b/Frontend/src/pages/admin/AdvancedAnalyticsPage.tsx index 7a582eee..1b6ac93a 100644 --- a/Frontend/src/pages/admin/AdvancedAnalyticsPage.tsx +++ b/Frontend/src/pages/admin/AdvancedAnalyticsPage.tsx @@ -40,6 +40,7 @@ import analyticsService, { import { SimpleBarChart, SimpleLineChart, SimplePieChart, KPICard } from '../../features/analytics/components/SimpleChart'; import { exportData } from '../../shared/utils/exportUtils'; import CustomReportBuilder from '../../features/analytics/components/CustomReportBuilder'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; type AnalyticsCategory = 'revenue' | 'operational' | 'guest' | 'financial' | 'comprehensive'; @@ -211,7 +212,7 @@ const AdvancedAnalyticsPage: React.FC = () => { title = 'Financial Analytics Report'; } else if (comprehensiveData) { // Export comprehensive data - exportDataArray = [comprehensiveData]; + exportDataArray = [comprehensiveData as unknown as Record]; filename = 'comprehensive-analytics'; title = 'Comprehensive Analytics Report'; } diff --git a/Frontend/src/pages/admin/AdvancedRoomManagementPage.tsx b/Frontend/src/pages/admin/AdvancedRoomManagementPage.tsx index 7ff4eddb..76e3e716 100644 --- a/Frontend/src/pages/admin/AdvancedRoomManagementPage.tsx +++ b/Frontend/src/pages/admin/AdvancedRoomManagementPage.tsx @@ -36,6 +36,7 @@ import apiClient from '../../shared/services/apiClient'; import { logger } from '../../shared/utils/logger'; import { useRoomContext } from '../../features/rooms/contexts/RoomContext'; import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; type Tab = 'status-board' | 'maintenance' | 'housekeeping' | 'inspections' | 'room-types'; @@ -80,8 +81,8 @@ const AdvancedRoomManagementPage: React.FC = () => { const [roomTypes, setRoomTypes] = useState>([]); const [uploadingImages, setUploadingImages] = useState(false); const [selectedFiles, setSelectedFiles] = useState([]); - const [customAmenityInput, setCustomAmenityInput] = useState(''); - const [editingAmenity, setEditingAmenity] = useState<{ name: string; newName: string } | null>(null); + const [_customAmenityInput, _setCustomAmenityInput] = useState(''); + const [_editingAmenity, _setEditingAmenity] = useState<{ name: string; newName: string } | null>(null); // Room Types management state const [roomTypesList, setRoomTypesList] = useState([]); @@ -671,92 +672,93 @@ const AdvancedRoomManagementPage: React.FC = () => { }); setSelectedFiles([]); setUploadingImages(false); - setCustomAmenityInput(''); - setEditingAmenity(null); + _setCustomAmenityInput(''); + _setEditingAmenity(null); }; // Amenities are now managed only at the room type level // Rooms automatically inherit amenities from their room type - const _handleAddCustomAmenity = () => { - const trimmed = customAmenityInput.trim(); - if (trimmed && !roomFormData.amenities.includes(trimmed)) { - setRoomFormData(prev => ({ - ...prev, - amenities: [...prev.amenities, trimmed] - })); - setCustomAmenityInput(''); - } - }; + // Unused functions - commented out to fix TypeScript errors + // const _handleAddCustomAmenity = () => { + // const trimmed = customAmenityInput.trim(); + // if (trimmed && !roomFormData.amenities.includes(trimmed)) { + // setRoomFormData(prev => ({ + // ...prev, + // amenities: [...prev.amenities, trimmed] + // })); + // setCustomAmenityInput(''); + // } + // }; - const _handleRemoveAmenity = (amenity: string) => { - setRoomFormData(prev => ({ - ...prev, - amenities: prev.amenities.filter(a => a !== amenity) - })); - }; + // const _handleRemoveAmenity = (amenity: string) => { + // setRoomFormData(prev => ({ + // ...prev, + // amenities: prev.amenities.filter(a => a !== amenity) + // })); + // }; - const _handleEditAmenity = (amenity: string) => { - setEditingAmenity({ name: amenity, newName: amenity }); - }; + // const _handleEditAmenity = (amenity: string) => { + // setEditingAmenity({ name: amenity, newName: amenity }); + // }; - const _handleSaveAmenityEdit = async () => { - if (!editingAmenity || editingAmenity.name === editingAmenity.newName.trim()) { - setEditingAmenity(null); - return; - } + // const _handleSaveAmenityEdit = async () => { + // if (!editingAmenity || editingAmenity.name === editingAmenity.newName.trim()) { + // setEditingAmenity(null); + // return; + // } - const newName = editingAmenity.newName.trim(); - if (!newName) { - toast.error('Amenity name cannot be empty'); - return; - } + // const newName = editingAmenity.newName.trim(); + // if (!newName) { + // toast.error('Amenity name cannot be empty'); + // return; + // } - try { - await roomService.updateAmenity(editingAmenity.name, newName); - toast.success(`Amenity "${editingAmenity.name}" updated to "${newName}"`); + // try { + // await roomService.updateAmenity(editingAmenity.name, newName); + // toast.success(`Amenity "${editingAmenity.name}" updated to "${newName}"`); - setAvailableAmenities(prev => { - const updated = prev.map(a => a === editingAmenity.name ? newName : a); - return updated.sort(); - }); + // setAvailableAmenities(prev => { + // const updated = prev.map(a => a === editingAmenity.name ? newName : a); + // return updated.sort(); + // }); - setRoomFormData(prev => ({ - ...prev, - amenities: prev.amenities.map(a => a === editingAmenity.name ? newName : a) - })); + // setRoomFormData(prev => ({ + // ...prev, + // amenities: prev.amenities.map(a => a === editingAmenity.name ? newName : a) + // })); - setEditingAmenity(null); - await fetchAvailableAmenities(); - await refreshRooms(); - await refreshStatusBoard(); - } catch (error: unknown) { - toast.error(getUserFriendlyError(error) || getUserFriendlyError(error) || 'Failed to update amenity'); - } - }; + // setEditingAmenity(null); + // await fetchAvailableAmenities(); + // await refreshRooms(); + // await refreshStatusBoard(); + // } catch (error: unknown) { + // toast.error(getUserFriendlyError(error) || 'Failed to update amenity'); + // } + // }; - const _handleDeleteAmenity = async (amenity: string) => { - if (!window.confirm(`Are you sure you want to delete "${amenity}"? This will remove it from all rooms and room types.`)) { - return; - } + // const _handleDeleteAmenity = async (amenity: string) => { + // if (!window.confirm(`Are you sure you want to delete "${amenity}"? This will remove it from all rooms and room types.`)) { + // return; + // } - try { - await roomService.deleteAmenity(amenity); - toast.success(`Amenity "${amenity}" deleted successfully`); + // try { + // await roomService.deleteAmenity(amenity); + // toast.success(`Amenity "${amenity}" deleted successfully`); - setAvailableAmenities(prev => prev.filter(a => a !== amenity)); - setRoomFormData(prev => ({ - ...prev, - amenities: prev.amenities.filter(a => a !== amenity) - })); + // setAvailableAmenities(prev => prev.filter(a => a !== amenity)); + // setRoomFormData(prev => ({ + // ...prev, + // amenities: prev.amenities.filter(a => a !== amenity) + // })); - await fetchAvailableAmenities(); - await refreshRooms(); - await refreshStatusBoard(); - } catch (error: unknown) { - toast.error(getUserFriendlyError(error) || getUserFriendlyError(error) || 'Failed to delete amenity'); - } - }; + // await fetchAvailableAmenities(); + // await refreshRooms(); + // await refreshStatusBoard(); + // } catch (error: unknown) { + // toast.error(getUserFriendlyError(error) || 'Failed to delete amenity'); + // } + // }; const handleFileSelect = (e: React.ChangeEvent) => { if (!e.target.files || e.target.files.length === 0) { @@ -896,38 +898,38 @@ const AdvancedRoomManagementPage: React.FC = () => { setEditingRoom(response.data.room); } catch (error: unknown) { logger.error('Error deleting image', error); - toast.error(getUserFriendlyError(error) || getUserFriendlyError(error) || 'Unable to delete image'); + toast.error(getUserFriendlyError(error) || 'Unable to delete image'); } }; - const _getRoomStatusBadge = (status: string) => { - const badges: Record = { - available: { - bg: 'bg-gradient-to-r from-emerald-50 to-green-50', - text: 'text-emerald-800', - label: 'Available', - border: 'border-emerald-200' - }, - occupied: { - bg: 'bg-gradient-to-r from-blue-50 to-indigo-50', - text: 'text-blue-800', - label: 'Occupied', - border: 'border-blue-200' - }, - maintenance: { - bg: 'bg-gradient-to-r from-amber-50 to-yellow-50', - text: 'text-amber-800', - label: 'Maintenance', - border: 'border-amber-200' - }, - }; - const badge = badges[status] || badges.available; - return ( - - {badge.label} - - ); - }; + // const _getRoomStatusBadge = (status: string) => { + // const badges: Record = { + // available: { + // bg: 'bg-gradient-to-r from-emerald-50 to-green-50', + // text: 'text-emerald-800', + // label: 'Available', + // border: 'border-emerald-200' + // }, + // occupied: { + // bg: 'bg-gradient-to-r from-blue-50 to-indigo-50', + // text: 'text-blue-800', + // label: 'Occupied', + // border: 'border-blue-200' + // }, + // maintenance: { + // bg: 'bg-gradient-to-r from-amber-50 to-yellow-50', + // text: 'text-amber-800', + // label: 'Maintenance', + // border: 'border-amber-200' + // }, + // }; + // const badge = badges[status] || badges.available; + // return ( + // + // {badge.label} + // + // ); + // }; if (statusBoardLoading && statusBoardRooms.length === 0 && activeTab === 'status-board') { return ; diff --git a/Frontend/src/pages/admin/AnalyticsDashboardPage.tsx b/Frontend/src/pages/admin/AnalyticsDashboardPage.tsx index 24eba768..3d75d355 100644 --- a/Frontend/src/pages/admin/AnalyticsDashboardPage.tsx +++ b/Frontend/src/pages/admin/AnalyticsDashboardPage.tsx @@ -62,6 +62,7 @@ import analyticsService, { import { SimpleBarChart, SimpleLineChart, SimplePieChart, KPICard } from '../../features/analytics/components/SimpleChart'; import { exportData } from '../../shared/utils/exportUtils'; import CustomReportBuilder from '../../features/analytics/components/CustomReportBuilder'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; type AnalyticsTab = 'overview' | 'reports' | 'revenue' | 'operational' | 'guest' | 'financial' | 'audit-logs'; @@ -835,7 +836,7 @@ const AnalyticsDashboardPage: React.FC = () => { setFormData({ ...formData, status: e.target.value as 'available' | 'occupied' | 'maintenance' | 'reserved' | 'out_of_order' })} + onChange={(e) => setFormData({ ...formData, status: e.target.value as 'available' | 'occupied' | 'maintenance' | 'cleaning' | 'reserved' })} className="w-full px-5 py-3.5 bg-gradient-to-br from-white to-slate-50/50 border-2 border-slate-200 rounded-2xl focus:border-amber-400 focus:ring-4 focus:ring-amber-100/50 transition-all duration-300 text-slate-800 font-medium shadow-sm hover:shadow-md focus:shadow-lg cursor-pointer appearance-none bg-[url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNiA5TDEyIDE1TDE4IDkiIHN0cm9rZT0iIzkyOUE1IiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvc3ZnPg==')] bg-[length:20px] bg-[right_1rem_center] bg-no-repeat pr-12" required > diff --git a/Frontend/src/pages/admin/EmailCampaignManagementPage.tsx b/Frontend/src/pages/admin/EmailCampaignManagementPage.tsx index 4863542a..26bd56f4 100644 --- a/Frontend/src/pages/admin/EmailCampaignManagementPage.tsx +++ b/Frontend/src/pages/admin/EmailCampaignManagementPage.tsx @@ -15,9 +15,51 @@ import { toast } from 'react-toastify'; import Loading from '../../shared/components/Loading'; import Pagination from '../../shared/components/Pagination'; import { formatDate } from '../../shared/utils/format'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; type CampaignTab = 'campaigns' | 'segments' | 'templates' | 'drip-sequences' | 'analytics'; +type CampaignFormData = { + name: string; + subject: string; + html_content: string; + text_content: string; + campaign_type: string; + segment_id: number | undefined; + scheduled_at: string; + template_id: number | undefined; + from_name: string; + from_email: string; + track_opens: boolean; + track_clicks: boolean; + recipient_type: 'users' | 'subscribers' | 'both'; +}; + +type SegmentFormData = { + name: string; + description: string; + criteria: { + role: string; + has_bookings: boolean | undefined; + is_vip: boolean | undefined; + last_booking_days: number | undefined; + }; +}; + +type TemplateFormData = { + name: string; + subject: string; + html_content: string; + text_content: string; + category: string; +}; + +type DripFormData = { + name: string; + description: string; + trigger_event: string; +}; + const EmailCampaignManagementPage: React.FC = () => { const [activeTab, setActiveTab] = useState('campaigns'); const [loading, setLoading] = useState(false); @@ -238,9 +280,10 @@ const EmailCampaignManagementPage: React.FC = () => { html_content: '', text_content: '', campaign_type: 'newsletter', - segment_id: undefined, + segment_id: undefined as number | undefined, scheduled_at: '', - template_id: undefined, + template_id: undefined as number | undefined, + recipient_type: 'users' as 'users' | 'subscribers' | 'both', from_name: '', from_email: '', track_opens: true, @@ -703,8 +746,8 @@ const AnalyticsTab: React.FC<{ analytics: CampaignAnalytics }> = ({ analytics }) ); const CampaignModal: React.FC<{ - form: typeof campaignForm; - setForm: (form: typeof campaignForm) => void; + form: CampaignFormData; + setForm: (form: CampaignFormData) => void; segments: CampaignSegment[]; templates: EmailTemplate[]; onSave: () => void; @@ -800,8 +843,8 @@ const CampaignModal: React.FC<{ ); const SegmentModal: React.FC<{ - form: typeof segmentForm; - setForm: (form: typeof segmentForm) => void; + form: SegmentFormData; + setForm: (form: SegmentFormData) => void; onSave: () => void; onClose: () => void; }> = ({ form, setForm, onSave, onClose }) => ( @@ -865,8 +908,8 @@ const SegmentModal: React.FC<{ ); const TemplateModal: React.FC<{ - form: typeof templateForm; - setForm: (form: typeof templateForm) => void; + form: TemplateFormData; + setForm: (form: TemplateFormData) => void; onSave: () => void; onClose: () => void; }> = ({ form, setForm, onSave, onClose }) => ( @@ -927,8 +970,8 @@ const TemplateModal: React.FC<{ ); const DripSequenceModal: React.FC<{ - form: typeof dripForm; - setForm: (form: typeof dripForm) => void; + form: DripFormData; + setForm: (form: DripFormData) => void; onSave: () => void; onClose: () => void; }> = ({ form, setForm, onSave, onClose }) => ( diff --git a/Frontend/src/pages/admin/FinancialAuditTrailPage.tsx b/Frontend/src/pages/admin/FinancialAuditTrailPage.tsx index 0062c960..7e25c101 100644 --- a/Frontend/src/pages/admin/FinancialAuditTrailPage.tsx +++ b/Frontend/src/pages/admin/FinancialAuditTrailPage.tsx @@ -12,6 +12,7 @@ import financialAuditService, { FinancialAuditRecord, FinancialAuditFilters } fr import { formatDate } from '../../shared/utils/format'; import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency'; import { exportData } from '../../shared/utils/exportUtils'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const FinancialAuditTrailPage: React.FC = () => { const { formatCurrency } = useFormatCurrency(); @@ -90,7 +91,11 @@ const FinancialAuditTrailPage: React.FC = () => { 'Created At': formatDate(record.created_at), })); - exportData(csvData, 'financial_audit_trail', 'csv'); + exportData({ + data: csvData, + filename: 'financial_audit_trail', + format: 'csv', + }); toast.success('Audit trail exported successfully'); } catch (error: unknown) { toast.error(getUserFriendlyError(error) || 'Unable to export audit trail'); diff --git a/Frontend/src/pages/admin/GDPRManagementPage.tsx b/Frontend/src/pages/admin/GDPRManagementPage.tsx index 258b62af..73aa6513 100644 --- a/Frontend/src/pages/admin/GDPRManagementPage.tsx +++ b/Frontend/src/pages/admin/GDPRManagementPage.tsx @@ -3,6 +3,8 @@ import { Download, Trash2 } from 'lucide-react'; import gdprService, { GDPRRequest } from '../../features/compliance/services/gdprService'; import { toast } from 'react-toastify'; import Loading from '../../shared/components/Loading'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; +import { formatDate } from '../../shared/utils/format'; const GDPRManagementPage: React.FC = () => { const [requests, setRequests] = useState([]); diff --git a/Frontend/src/pages/admin/GroupBookingManagementPage.tsx b/Frontend/src/pages/admin/GroupBookingManagementPage.tsx index 0a02f889..ff872be1 100644 --- a/Frontend/src/pages/admin/GroupBookingManagementPage.tsx +++ b/Frontend/src/pages/admin/GroupBookingManagementPage.tsx @@ -7,6 +7,7 @@ import Pagination from '../../shared/components/Pagination'; import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency'; import { formatDate } from '../../shared/utils/format'; import CreateGroupBookingModal from '../../features/hotel_services/components/CreateGroupBookingModal'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const GroupBookingManagementPage: React.FC = () => { const { formatCurrency } = useFormatCurrency(); diff --git a/Frontend/src/pages/admin/GuestProfilePage.tsx b/Frontend/src/pages/admin/GuestProfilePage.tsx index da415903..f5a122a3 100644 --- a/Frontend/src/pages/admin/GuestProfilePage.tsx +++ b/Frontend/src/pages/admin/GuestProfilePage.tsx @@ -22,6 +22,7 @@ import Loading from '../../shared/components/Loading'; import Pagination from '../../shared/components/Pagination'; import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency'; import { logger } from '../../shared/utils/logger'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; type TabType = 'list' | 'profile'; @@ -108,7 +109,7 @@ const GuestProfilePage: React.FC = () => { setCurrentPage(1); }; - const handleFilterChange = (key: keyof GuestSearchParams, value: string | number | undefined) => { + const handleFilterChange = (key: keyof GuestSearchParams, value: string | number | boolean | undefined) => { setFilters({ ...filters, [key]: value, page: 1 }); setCurrentPage(1); }; diff --git a/Frontend/src/pages/admin/InspectionManagementPage.tsx b/Frontend/src/pages/admin/InspectionManagementPage.tsx index 1f24ada0..2cdd9f98 100644 --- a/Frontend/src/pages/admin/InspectionManagementPage.tsx +++ b/Frontend/src/pages/admin/InspectionManagementPage.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { Search, Plus, Edit, Eye, CheckCircle, Clock, X, FileCheck, AlertCircle } from 'lucide-react'; -import advancedRoomService, { RoomInspection } from '../../features/rooms/services/advancedRoomService'; +import advancedRoomService, { RoomInspection, InspectionChecklistItem } from '../../features/rooms/services/advancedRoomService'; import roomService, { Room } from '../../features/rooms/services/roomService'; import userService, { User } from '../../features/auth/services/userService'; import { toast } from 'react-toastify'; @@ -8,6 +8,7 @@ import Loading from '../../shared/components/Loading'; import Pagination from '../../shared/components/Pagination'; import { formatDate } from '../../shared/utils/format'; import DatePicker from 'react-datepicker'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; import 'react-datepicker/dist/react-datepicker.css'; const InspectionManagementPage: React.FC = () => { @@ -37,7 +38,7 @@ const InspectionManagementPage: React.FC = () => { inspection_type: 'routine', scheduled_at: new Date(), inspected_by: 0, - checklist_items: [] as Array<{ id?: number; item: string; checked: boolean; notes?: string }>, + checklist_items: [] as InspectionChecklistItem[], }); const inspectionTypes = [ @@ -147,7 +148,14 @@ const InspectionManagementPage: React.FC = () => { toast.success('Inspection updated successfully'); } else { // Create new inspection - const dataToSubmit: Record = { + const dataToSubmit: { + room_id: number; + inspection_type: string; + scheduled_at: string; + checklist_items: InspectionChecklistItem[]; + booking_id?: number; + inspected_by?: number; + } = { room_id: formData.room_id, inspection_type: formData.inspection_type, scheduled_at: formData.scheduled_at.toISOString(), @@ -531,7 +539,11 @@ const InspectionManagementPage: React.FC = () => { setFormData({ ...formData, scheduled_at: date })} + onChange={(date: Date | null) => { + if (date) { + setFormData({ ...formData, scheduled_at: date }); + } + }} showTimeSelect dateFormat="MMMM d, yyyy h:mm aa" className="w-full px-4 py-2 border-2 border-slate-200 rounded-xl focus:border-amber-400" diff --git a/Frontend/src/pages/admin/InventoryManagementPage.tsx b/Frontend/src/pages/admin/InventoryManagementPage.tsx index 0fe5a217..dd6b8d94 100644 --- a/Frontend/src/pages/admin/InventoryManagementPage.tsx +++ b/Frontend/src/pages/admin/InventoryManagementPage.tsx @@ -5,6 +5,7 @@ import { toast } from 'react-toastify'; import Loading from '../../shared/components/Loading'; import Pagination from '../../shared/components/Pagination'; import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const InventoryManagementPage: React.FC = () => { const { formatCurrency } = useFormatCurrency(); diff --git a/Frontend/src/pages/admin/InvoiceEditPage.tsx b/Frontend/src/pages/admin/InvoiceEditPage.tsx index a583dd87..b642c2e5 100644 --- a/Frontend/src/pages/admin/InvoiceEditPage.tsx +++ b/Frontend/src/pages/admin/InvoiceEditPage.tsx @@ -6,6 +6,7 @@ import { toast } from 'react-toastify'; import Loading from '../../shared/components/Loading'; import { validateInvoiceId } from '../../shared/utils/routeValidation'; import { logger } from '../../shared/utils/logger'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const InvoiceEditPage: React.FC = () => { const { id } = useParams<{ id: string }>(); diff --git a/Frontend/src/pages/admin/InvoiceManagementPage.tsx b/Frontend/src/pages/admin/InvoiceManagementPage.tsx index 8b28c4d2..c8f6d418 100644 --- a/Frontend/src/pages/admin/InvoiceManagementPage.tsx +++ b/Frontend/src/pages/admin/InvoiceManagementPage.tsx @@ -8,6 +8,7 @@ import ExportButton from '../../shared/components/ExportButton'; import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency'; import { useNavigate } from 'react-router-dom'; import { formatDate } from '../../shared/utils/format'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const InvoiceManagementPage: React.FC = () => { const { formatCurrency } = useFormatCurrency(); diff --git a/Frontend/src/pages/admin/LoyaltyManagementPage.tsx b/Frontend/src/pages/admin/LoyaltyManagementPage.tsx index 92d13671..e86828bc 100644 --- a/Frontend/src/pages/admin/LoyaltyManagementPage.tsx +++ b/Frontend/src/pages/admin/LoyaltyManagementPage.tsx @@ -20,6 +20,7 @@ import ConfirmationDialog from '../../shared/components/ConfirmationDialog'; import loyaltyService, { LoyaltyTier, LoyaltyReward } from '../../features/loyalty/services/loyaltyService'; import Pagination from '../../shared/components/Pagination'; import { logger } from '../../shared/utils/logger'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; type Tab = 'users' | 'tiers' | 'rewards'; diff --git a/Frontend/src/pages/admin/MaintenanceManagementPage.tsx b/Frontend/src/pages/admin/MaintenanceManagementPage.tsx index f2a74f28..2a8eb0a8 100644 --- a/Frontend/src/pages/admin/MaintenanceManagementPage.tsx +++ b/Frontend/src/pages/admin/MaintenanceManagementPage.tsx @@ -8,6 +8,8 @@ import Loading from '../../shared/components/Loading'; import Pagination from '../../shared/components/Pagination'; import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency'; import DatePicker from 'react-datepicker'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; +import { formatDate } from '../../shared/utils/format'; import 'react-datepicker/dist/react-datepicker.css'; const MaintenanceManagementPage: React.FC = () => { @@ -87,7 +89,7 @@ const MaintenanceManagementPage: React.FC = () => { const fetchRecords = async () => { try { setLoading(true); - const params: { page: number; limit: number; room_id?: number; status?: string } = { + const params: { page: number; limit: number; room_id?: number; status?: string; maintenance_type?: string; priority?: string } = { page: currentPage, limit: itemsPerPage, }; @@ -163,7 +165,21 @@ const MaintenanceManagementPage: React.FC = () => { } try { - const dataToSubmit: Record = { + const dataToSubmit: { + room_id: number; + maintenance_type: string; + title: string; + description?: string; + scheduled_start: string; + scheduled_end?: string; + assigned_to?: number; + estimated_cost?: number; + blocks_room: boolean; + block_start?: string; + block_end?: string; + priority: string; + notes?: string; + } = { room_id: formData.room_id, maintenance_type: formData.maintenance_type, title: formData.title, @@ -637,7 +653,11 @@ const MaintenanceManagementPage: React.FC = () => { setFormData({ ...formData, scheduled_start: date })} + onChange={(date: Date | null) => { + if (date) { + setFormData({ ...formData, scheduled_start: date }); + } + }} showTimeSelect dateFormat="MMMM d, yyyy h:mm aa" className="w-full px-4 py-2 border-2 border-slate-200 rounded-xl focus:border-amber-400 focus:ring-4 focus:ring-amber-100" diff --git a/Frontend/src/pages/admin/PackageManagementPage.tsx b/Frontend/src/pages/admin/PackageManagementPage.tsx index 2146a287..313e38ba 100644 --- a/Frontend/src/pages/admin/PackageManagementPage.tsx +++ b/Frontend/src/pages/admin/PackageManagementPage.tsx @@ -8,6 +8,7 @@ import Loading from '../../shared/components/Loading'; import Pagination from '../../shared/components/Pagination'; import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency'; import { logger } from '../../shared/utils/logger'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const PackageManagementPage: React.FC = () => { const { formatCurrency } = useFormatCurrency(); diff --git a/Frontend/src/pages/admin/PaymentManagementPage.tsx b/Frontend/src/pages/admin/PaymentManagementPage.tsx index 0a1c7d38..6bcafee2 100644 --- a/Frontend/src/pages/admin/PaymentManagementPage.tsx +++ b/Frontend/src/pages/admin/PaymentManagementPage.tsx @@ -9,6 +9,8 @@ import ExportButton from '../../shared/components/ExportButton'; import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency'; import { formatDate } from '../../shared/utils/format'; import { PAYMENT_STATUS } from '../../shared/constants/bookingConstants'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; +import { logger } from '../../shared/utils/logger'; const PaymentManagementPage: React.FC = () => { const { formatCurrency } = useFormatCurrency(); @@ -17,6 +19,7 @@ const PaymentManagementPage: React.FC = () => { const [filters, setFilters] = useState({ search: '', method: '', + status: '', from: '', to: '', }); @@ -388,15 +391,15 @@ const PaymentManagementPage: React.FC = () => {

Total Revenue

{formatCurrency(payments - .filter(p => p.payment_status === PAYMENT_STATUS.PAID || p.payment_status === 'completed') + .filter(p => p.payment_status === 'completed') .reduce((sum, p) => sum + p.amount, 0))}

- Total {payments.filter(p => p.payment_status === PAYMENT_STATUS.PAID || p.payment_status === 'completed').length} paid transaction{payments.filter(p => p.payment_status === PAYMENT_STATUS.PAID || p.payment_status === 'completed').length !== 1 ? 's' : ''} + Total {payments.filter(p => p.payment_status === 'completed').length} paid transaction{payments.filter(p => p.payment_status === 'completed').length !== 1 ? 's' : ''}

-
{payments.filter(p => p.payment_status === PAYMENT_STATUS.PAID || p.payment_status === 'completed').length}
+
{payments.filter(p => p.payment_status === 'completed').length}
diff --git a/Frontend/src/pages/admin/ProfilePage.tsx b/Frontend/src/pages/admin/ProfilePage.tsx index 1babe0a5..90660627 100644 --- a/Frontend/src/pages/admin/ProfilePage.tsx +++ b/Frontend/src/pages/admin/ProfilePage.tsx @@ -40,7 +40,9 @@ import { useGlobalLoading } from '../../shared/contexts/LoadingContext'; import { normalizeImageUrl } from '../../shared/utils/imageUtils'; import { formatDate } from '../../shared/utils/format'; import { UserSession } from '../../features/auth/services/sessionService'; -import { useNavigate } from 'react-router-dom'; +// import { useNavigate } from 'react-router-dom'; // Unused +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; +import { isAxiosError } from 'axios'; const profileValidationSchema = yup.object().shape({ name: yup @@ -85,7 +87,7 @@ type PasswordFormData = yup.InferType; const ProfilePage: React.FC = () => { const { userInfo, setUser } = useAuthStore(); const { setLoading } = useGlobalLoading(); - const _navigate = useNavigate(); + // const _navigate = useNavigate(); // Unused const [activeTab, setActiveTab] = useState<'profile' | 'password' | 'mfa' | 'sessions' | 'gdpr'>('profile'); const [avatarPreview, setAvatarPreview] = useState(null); const [avatarError, setAvatarError] = useState(false); @@ -107,8 +109,8 @@ const ProfilePage: React.FC = () => { const [showBackupCodes, setShowBackupCodes] = useState(null); const [showMfaSecret, setShowMfaSecret] = useState(false); const mfaAbortControllerRef = useRef(null); - const _sessionsAbortControllerRef = useRef(null); - const _gdprAbortControllerRef = useRef(null); + // const _sessionsAbortControllerRef = useRef(null); // Reserved for future use + // const _gdprAbortControllerRef = useRef(null); // Reserved for future use const fetchProfile = async () => { @@ -244,8 +246,16 @@ const ProfilePage: React.FC = () => { if (response.status === 'success' || response.success) { const updatedUser = response.data?.user || response.data; - if (updatedUser) { - setUser(updatedUser); + if (updatedUser && typeof updatedUser === 'object' && 'id' in updatedUser) { + setUser({ + id: (updatedUser as { id: number }).id, + name: ((updatedUser as { name?: string; full_name?: string }).name || (updatedUser as { name?: string; full_name?: string }).full_name || '') as string, + email: (updatedUser as { email: string }).email, + phone: ((updatedUser as { phone?: string; phone_number?: string }).phone || (updatedUser as { phone?: string; phone_number?: string }).phone_number) as string | undefined, + avatar: (updatedUser as { avatar?: string }).avatar, + role: (updatedUser as { role: string }).role, + createdAt: ((updatedUser as { createdAt?: string; created_at?: string }).createdAt || (updatedUser as { createdAt?: string; created_at?: string }).created_at) as string | undefined, + }); toast.success('Profile updated successfully!'); refetchProfile(); } @@ -1161,7 +1171,7 @@ const SessionsTab: React.FC = () => { fetchSessions(); } } catch (error: unknown) { - if (getUserFriendlyError(error) === 401) { + if (isAxiosError(error) && error.response?.status === 401) { toast.warning('Your session has been revoked. You will be logged out.'); setTimeout(() => { window.location.href = '/'; @@ -1187,7 +1197,7 @@ const SessionsTab: React.FC = () => { fetchSessions(); } } catch (error: unknown) { - if (getUserFriendlyError(error) === 401) { + if (isAxiosError(error) && error.response?.status === 401) { toast.warning('All sessions have been revoked. You will be logged out.'); setTimeout(() => { window.location.href = '/'; diff --git a/Frontend/src/pages/admin/PromotionManagementPage.tsx b/Frontend/src/pages/admin/PromotionManagementPage.tsx index 46ebe3c9..a94f7f5e 100644 --- a/Frontend/src/pages/admin/PromotionManagementPage.tsx +++ b/Frontend/src/pages/admin/PromotionManagementPage.tsx @@ -134,7 +134,7 @@ const PromotionManagementPage: React.FC = () => { await promotionService.updatePromotion(editingPromotion.id, submitData); toast.success('Promotion updated successfully'); } else { - await promotionService.createPromotion(submitData); + await promotionService.createPromotion(submitData as CreatePromotionData); toast.success('Promotion added successfully'); } setShowModal(false); diff --git a/Frontend/src/pages/admin/PromotionsManagementPage.tsx b/Frontend/src/pages/admin/PromotionsManagementPage.tsx index 46a52a68..4f259d48 100644 --- a/Frontend/src/pages/admin/PromotionsManagementPage.tsx +++ b/Frontend/src/pages/admin/PromotionsManagementPage.tsx @@ -13,8 +13,9 @@ import Loading from '../../shared/components/Loading'; import Pagination from '../../shared/components/Pagination'; import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency'; import { useCurrency } from '../../features/payments/contexts/CurrencyContext'; -import promotionService, { Promotion } from '../../features/loyalty/services/promotionService'; +import promotionService, { Promotion, CreatePromotionData } from '../../features/loyalty/services/promotionService'; import { getRoomTypes } from '../../features/rooms/services/roomService'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; interface RoomType { id: number; @@ -137,7 +138,7 @@ const PromotionsManagementPage: React.FC = () => { await promotionService.updatePromotion(editingPromotion.id, submitData); toast.success('Promotion updated successfully'); } else { - await promotionService.createPromotion(submitData); + await promotionService.createPromotion(submitData as unknown as CreatePromotionData); toast.success('Promotion added successfully'); } setShowPromotionModal(false); diff --git a/Frontend/src/pages/admin/RatePlanManagementPage.tsx b/Frontend/src/pages/admin/RatePlanManagementPage.tsx index d7ece928..663e3eb5 100644 --- a/Frontend/src/pages/admin/RatePlanManagementPage.tsx +++ b/Frontend/src/pages/admin/RatePlanManagementPage.tsx @@ -7,6 +7,7 @@ import Loading from '../../shared/components/Loading'; import Pagination from '../../shared/components/Pagination'; import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency'; import { logger } from '../../shared/utils/logger'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const RatePlanManagementPage: React.FC = () => { const { formatCurrency } = useFormatCurrency(); diff --git a/Frontend/src/pages/admin/ReceptionDashboardPage.tsx b/Frontend/src/pages/admin/ReceptionDashboardPage.tsx index 1cbeaf49..d53cdd65 100644 --- a/Frontend/src/pages/admin/ReceptionDashboardPage.tsx +++ b/Frontend/src/pages/admin/ReceptionDashboardPage.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect, useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; import { LogIn, LogOut, @@ -26,6 +27,7 @@ import Pagination from '../../shared/components/Pagination'; import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency'; import { parseDateLocal } from '../../shared/utils/format'; import CreateBookingModal from '../../features/hotel_services/components/CreateBookingModal'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; type ReceptionTab = 'overview' | 'check-in' | 'check-out' | 'bookings'; @@ -43,6 +45,7 @@ interface ServiceItem { } const ReceptionDashboardPage: React.FC = () => { + const navigate = useNavigate(); const { formatCurrency } = useFormatCurrency(); const [activeTab, setActiveTab] = useState('overview'); @@ -313,7 +316,7 @@ const ReceptionDashboardPage: React.FC = () => { const handleUpdateBookingStatus = async (id: number, status: string) => { try { setUpdatingBookingId(id); - await bookingService.updateBooking(id, { status } as { status: string }); + await bookingService.updateBooking(id, { status: status as Booking['status'] }); toast.success('Status updated successfully'); await fetchBookings(); } catch (error: unknown) { @@ -1599,13 +1602,13 @@ const ReceptionDashboardPage: React.FC = () => {

- {payment.payment_status === 'completed' || payment.payment_status === 'paid' ? 'Paid' : payment.payment_status || 'Pending'} + {payment.payment_status === 'completed' ? 'Paid' : payment.payment_status || 'Pending'}
{payment.transaction_id && ( diff --git a/Frontend/src/pages/admin/ReviewManagementPage.tsx b/Frontend/src/pages/admin/ReviewManagementPage.tsx index 698321bb..147e5187 100644 --- a/Frontend/src/pages/admin/ReviewManagementPage.tsx +++ b/Frontend/src/pages/admin/ReviewManagementPage.tsx @@ -3,6 +3,7 @@ import reviewService, { Review } from '../../features/reviews/services/reviewSer import { toast } from 'react-toastify'; import Loading from '../../shared/components/Loading'; import Pagination from '../../shared/components/Pagination'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const ReviewManagementPage: React.FC = () => { const [reviews, setReviews] = useState([]); diff --git a/Frontend/src/pages/admin/SecurityManagementPage.tsx b/Frontend/src/pages/admin/SecurityManagementPage.tsx index 5b442bb7..e3c99b6e 100644 --- a/Frontend/src/pages/admin/SecurityManagementPage.tsx +++ b/Frontend/src/pages/admin/SecurityManagementPage.tsx @@ -14,12 +14,13 @@ import { AlertCircle, Info } from 'lucide-react'; -import { securityService, SecurityEvent, SecurityStats, OAuthProvider, DataSubjectRequest } from '../../features/security/services/securityService'; +import { securityService, SecurityEvent, SecurityStats, OAuthProvider, DataSubjectRequest, IPBlacklist } from '../../features/security/services/securityService'; import { toast } from 'react-toastify'; import Loading from '../../shared/components/Loading'; import Pagination from '../../shared/components/Pagination'; import { formatDate } from '../../shared/utils/format'; import { logger } from '../../shared/utils/logger'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; type SecurityTab = 'events' | 'stats' | 'ip-whitelist' | 'ip-blacklist' | 'oauth' | 'gdpr' | 'scan'; @@ -595,7 +596,7 @@ const IPWhitelistTab: React.FC = () => { // IP Blacklist Tab Component const IPBlacklistTab: React.FC = () => { - const [ips, setIPs] = useState>([]); + const [ips, setIPs] = useState([]); const [, setLoading] = useState(false); const [showAddModal, setShowAddModal] = useState(false); const [newIP, setNewIP] = useState({ ip_address: '', reason: '' }); @@ -1122,7 +1123,7 @@ const OAuthProvidersTab: React.FC = () => { const GDPRRequestsTab: React.FC = () => { const [requests, setRequests] = useState([]); const [loading, setLoading] = useState(false); - const [_selectedRequest, setSelectedRequest] = useState(null); + const [selectedRequest, setSelectedRequest] = useState(null); const [filters, setFilters] = useState({ status: '', request_type: '' @@ -1419,7 +1420,23 @@ const GDPRRequestsTab: React.FC = () => { // Security Scan Tab Component const SecurityScanTab: React.FC = () => { const [scanning, setScanning] = useState(false); - const [scanResults, setScanResults] = useState | null>(null); + const [scanResults, setScanResults] = useState<{ + duration_seconds?: number; + critical_issues?: number; + high_issues?: number; + medium_issues?: number; + low_issues?: number; + total_issues?: number; + checks?: Array<{ + check_name?: string; + name?: string; + status?: string; + severity?: string; + description?: string; + recommendation?: string; + issue_count?: number; + }>; + } | null>(null); const [scheduleInterval, setScheduleInterval] = useState(24); const [scheduled, setScheduled] = useState(false); @@ -1557,7 +1574,7 @@ const SecurityScanTab: React.FC = () => { {/* Check Results */}
- {scanResults.checks?.map((check: { name?: string; status?: string; message?: string; severity?: string }, index: number) => ( + {scanResults.checks?.map((check: { name?: string; check_name?: string; status?: string; message?: string; severity?: string; description?: string; recommendation?: string; issue_count?: number }, index: number) => (
{
{check.check_name}
- - {check.severity} + + {check.severity || 'low'} { 💡 Recommendation: {check.recommendation}

)} - {check.issue_count > 0 && ( + {(check.issue_count ?? 0) > 0 && (

- Issues found: {check.issue_count} + Issues found: {check.issue_count ?? 0}

)}
diff --git a/Frontend/src/pages/admin/ServiceManagementPage.tsx b/Frontend/src/pages/admin/ServiceManagementPage.tsx index 015d65b0..aad18a11 100644 --- a/Frontend/src/pages/admin/ServiceManagementPage.tsx +++ b/Frontend/src/pages/admin/ServiceManagementPage.tsx @@ -1,12 +1,13 @@ import React, { useEffect, useState } from 'react'; import { Plus, Search, Edit, Trash2, X, Upload } from 'lucide-react'; -import serviceService, { Service } from '../../features/hotel_services/services/serviceService'; +import serviceService, { Service, UpdateServiceData, CreateServiceData } from '../../features/hotel_services/services/serviceService'; import { ServiceSection } from '../../features/content/pages/ServiceDetailPage'; import { toast } from 'react-toastify'; import Loading from '../../shared/components/Loading'; import Pagination from '../../shared/components/Pagination'; import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency'; import ConfirmationDialog from '../../shared/components/ConfirmationDialog'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const ServiceManagementPage: React.FC = () => { const { formatCurrency } = useFormatCurrency(); @@ -83,10 +84,10 @@ const ServiceManagementPage: React.FC = () => { }; if (editingService) { - await serviceService.updateService(editingService.id, dataToSubmit); + await serviceService.updateService(editingService.id, dataToSubmit as UpdateServiceData); toast.success('Service updated successfully'); } else { - await serviceService.createService(dataToSubmit); + await serviceService.createService(dataToSubmit as CreateServiceData); toast.success('Service added successfully'); } setShowModal(false); @@ -522,7 +523,7 @@ const ServiceManagementPage: React.FC = () => { value={section?.type || 'text'} onChange={(e) => { const updatedSections = [...formData.sections]; - updatedSections[sectionIndex] = { ...updatedSections[sectionIndex], type: e.target.value }; + updatedSections[sectionIndex] = { ...updatedSections[sectionIndex], type: e.target.value as ServiceSection['type'] }; setFormData({ ...formData, sections: updatedSections }); }} className="px-3 py-1.5 border border-slate-300 rounded text-sm" diff --git a/Frontend/src/pages/admin/SettingsPage.tsx b/Frontend/src/pages/admin/SettingsPage.tsx index 0515da14..94c88d41 100644 --- a/Frontend/src/pages/admin/SettingsPage.tsx +++ b/Frontend/src/pages/admin/SettingsPage.tsx @@ -48,6 +48,7 @@ import { recaptchaService, RecaptchaSettingsAdminResponse, UpdateRecaptchaSettin import { useCurrency } from '../../features/payments/contexts/CurrencyContext'; import Loading from '../../shared/components/Loading'; import { getCurrencySymbol } from '../../shared/utils/format'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; type SettingsTab = 'general' | 'cookie' | 'currency' | 'payment' | 'smtp' | 'company' | 'recaptcha' | 'theme'; diff --git a/Frontend/src/pages/admin/StaffShiftDashboardPage.tsx b/Frontend/src/pages/admin/StaffShiftDashboardPage.tsx index 825d79f1..e9864a61 100644 --- a/Frontend/src/pages/admin/StaffShiftDashboardPage.tsx +++ b/Frontend/src/pages/admin/StaffShiftDashboardPage.tsx @@ -11,6 +11,7 @@ import Loading from '../../shared/components/Loading'; import Pagination from '../../shared/components/Pagination'; import { formatDate } from '../../shared/utils/format'; import DatePicker from 'react-datepicker'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; import 'react-datepicker/dist/react-datepicker.css'; // Custom scrollbar styles - Luxury design @@ -148,7 +149,7 @@ const StaffShiftDashboardPage: React.FC = () => { const fetchShifts = async () => { try { setLoading(true); - const params: { page: number; limit: number; status?: string; staff_id?: number } = { page: currentPage, limit: itemsPerPage }; + const params: { page: number; limit: number; status?: string; staff_id?: number; shift_date?: string; department?: string } = { page: currentPage, limit: itemsPerPage }; if (filters.status) params.status = filters.status; if (filters.staff_id) params.staff_id = parseInt(filters.staff_id); if (filters.shift_date) params.shift_date = filters.shift_date; diff --git a/Frontend/src/pages/admin/StripeSettingsPage.tsx b/Frontend/src/pages/admin/StripeSettingsPage.tsx index ffdc4f4e..445d360e 100644 --- a/Frontend/src/pages/admin/StripeSettingsPage.tsx +++ b/Frontend/src/pages/admin/StripeSettingsPage.tsx @@ -6,6 +6,7 @@ import systemSettingsService, { UpdateStripeSettingsRequest, } from '../../features/system/services/systemSettingsService'; import Loading from '../../shared/components/Loading'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const StripeSettingsPage: React.FC = () => { const [settings, setSettings] = useState(null); diff --git a/Frontend/src/pages/admin/TaskManagementPage.tsx b/Frontend/src/pages/admin/TaskManagementPage.tsx index 2bfcb4bb..97112c1a 100644 --- a/Frontend/src/pages/admin/TaskManagementPage.tsx +++ b/Frontend/src/pages/admin/TaskManagementPage.tsx @@ -8,6 +8,7 @@ import { Calendar, User, Play, + Clock, } from 'lucide-react'; import { toast } from 'react-toastify'; import Loading from '../../shared/components/Loading'; @@ -19,6 +20,7 @@ import TaskDetailModal from '../../features/system/components/TaskDetailModal'; import CreateTaskModal from '../../features/system/components/CreateTaskModal'; import TaskFilters from '../../features/system/components/TaskFilters'; import { logger } from '../../shared/utils/logger'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; type TaskStatus = 'pending' | 'assigned' | 'in_progress' | 'completed' | 'cancelled' | 'overdue'; type TaskPriority = 'low' | 'medium' | 'high' | 'urgent'; @@ -61,8 +63,8 @@ const TaskManagementPage: React.FC = () => { const { data: statistics, execute: fetchStatistics } = useAsync( async () => { const r = await taskService.getTaskStatistics(); - const responseData = r as { data?: { data?: Task[] }; status?: string }; - return responseData.data?.data || (r.data as Task[]); + const responseData = r.data as { status?: string; data?: TaskStatistics }; + return (responseData.data || r.data) as TaskStatistics; }, { immediate: true } ); diff --git a/Frontend/src/pages/admin/UserManagementPage.tsx b/Frontend/src/pages/admin/UserManagementPage.tsx index 378dca34..13b02332 100644 --- a/Frontend/src/pages/admin/UserManagementPage.tsx +++ b/Frontend/src/pages/admin/UserManagementPage.tsx @@ -10,6 +10,8 @@ import useAuthStore from '../../store/useAuthStore'; import { logger } from '../../shared/utils/logger'; import { useApiCall } from '../../shared/hooks/useApiCall'; import { useStepUpAuth } from '../../features/auth/contexts/StepUpAuthContext'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; +import { isAxiosError } from 'axios'; const UserManagementPage: React.FC = () => { const { userInfo } = useAuthStore(); @@ -23,11 +25,13 @@ const UserManagementPage: React.FC = () => { const pendingSubmitDataRef = useRef<{ data: Record; isEdit: boolean } | null>(null); const { execute: executeSubmit, isLoading: isSubmitting } = useApiCall( - async (data: Record, isEdit: boolean) => { + async (...args: unknown[]) => { + const data = args[0] as Record; + const isEdit = args[1] as boolean; if (isEdit && editingUser) { - return await userService.updateUser(editingUser.id, data); + return await userService.updateUser(editingUser.id, data as Parameters[1]); } else { - return await userService.createUser(data); + return await userService.createUser(data as unknown as Parameters[0]); } }, { @@ -145,33 +149,33 @@ const UserManagementPage: React.FC = () => { // Check if step-up authentication is required // Check both the original response structure and the modified error from API client - const errorData = error.response?.data; + const errorObj = error as { response?: { data?: { detail?: unknown } }; requiresStepUp?: boolean; stepUpAction?: string }; + const errorData = errorObj.response?.data; const errorDetail = errorData?.detail; // Check for step-up required in multiple ways const isStepUpRequired = - error.requiresStepUp === true || - error.stepUpAction !== undefined || - (getUserFriendlyError(error) === 403 && - (errorDetail?.error === 'step_up_required' || - errorData?.error === 'step_up_required' || - (typeof errorDetail === 'object' && errorDetail?.error === 'step_up_required') || + errorObj.requiresStepUp === true || + errorObj.stepUpAction !== undefined || + (isAxiosError(error) && error.response?.status === 403 && + ((typeof errorDetail === 'object' && errorDetail !== null && 'error' in errorDetail && (errorDetail as { error?: string }).error === 'step_up_required') || + (typeof errorData === 'object' && errorData !== null && 'error' in errorData && (errorData as { error?: string }).error === 'step_up_required') || (typeof errorDetail === 'string' && errorDetail.includes('Step-up authentication required')))); if (isStepUpRequired) { const actionDescription = - error.stepUpAction || - (typeof errorDetail === 'object' ? errorDetail?.action : null) || - errorDetail?.action || + errorObj.stepUpAction || + (typeof errorDetail === 'object' && errorDetail !== null && 'action' in errorDetail ? (errorDetail as { action?: string }).action : null) || + (typeof errorDetail === 'object' && errorDetail !== null && 'action' in errorDetail ? (errorDetail as { action?: string }).action : null) || (typeof errorDetail === 'string' ? errorDetail : null) || - errorDetail?.message || + (typeof errorDetail === 'object' && errorDetail !== null && 'message' in errorDetail ? (errorDetail as { message?: string }).message : null) || (editingUser ? 'user update' : 'user creation'); logger.debug('Step-up required, opening modal', { actionDescription, error: { - requiresStepUp: error.requiresStepUp, - stepUpAction: error.stepUpAction, + requiresStepUp: errorObj.requiresStepUp, + stepUpAction: errorObj.stepUpAction, status: getUserFriendlyError(error), detail: errorDetail } diff --git a/Frontend/src/pages/admin/WebhookManagementPage.tsx b/Frontend/src/pages/admin/WebhookManagementPage.tsx index 369528a8..4d2349dd 100644 --- a/Frontend/src/pages/admin/WebhookManagementPage.tsx +++ b/Frontend/src/pages/admin/WebhookManagementPage.tsx @@ -4,6 +4,7 @@ import webhookService, { Webhook, CreateWebhookData, UpdateWebhookData } from '. import { toast } from 'react-toastify'; import Loading from '../../shared/components/Loading'; import { formatDate } from '../../shared/utils/format'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const WebhookManagementPage: React.FC = () => { const [webhooks, setWebhooks] = useState([]); diff --git a/Frontend/src/pages/admin/WorkflowManagementPage.tsx b/Frontend/src/pages/admin/WorkflowManagementPage.tsx index 45d9aa4e..20b9752e 100644 --- a/Frontend/src/pages/admin/WorkflowManagementPage.tsx +++ b/Frontend/src/pages/admin/WorkflowManagementPage.tsx @@ -15,6 +15,7 @@ import { useAsync } from '../../shared/hooks/useAsync'; import workflowService, { Workflow } from '../../features/system/services/workflowService'; import WorkflowBuilder from '../../features/system/components/WorkflowBuilder'; import WorkflowDetailModal from '../../features/system/components/WorkflowDetailModal'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const WorkflowManagementPage: React.FC = () => { const [showBuilder, setShowBuilder] = useState(false); diff --git a/Frontend/src/pages/admin/index.ts b/Frontend/src/pages/admin/index.ts index df881502..62e74d29 100644 --- a/Frontend/src/pages/admin/index.ts +++ b/Frontend/src/pages/admin/index.ts @@ -8,7 +8,7 @@ export { default as AdminDashboardPage } from './DashboardPage'; export { default as UserManagementPage } from './UserManagementPage'; export { default as GuestProfilePage } from './GuestProfilePage'; export { default as GroupBookingManagementPage } from './GroupBookingManagementPage'; -export { default as BusinessDashboardPage } from './BusinessDashboardPage'; +// export { default as BusinessDashboardPage } from './BusinessDashboardPage'; // File does not exist export { default as ReceptionDashboardPage } from './ReceptionDashboardPage'; export { default as AdvancedRoomManagementPage } from './AdvancedRoomManagementPage'; export { default as PageContentDashboardPage } from './PageContentDashboard'; diff --git a/Frontend/src/pages/customer/BookingDetailPage.tsx b/Frontend/src/pages/customer/BookingDetailPage.tsx index 2e1bf388..1dd0c625 100644 --- a/Frontend/src/pages/customer/BookingDetailPage.tsx +++ b/Frontend/src/pages/customer/BookingDetailPage.tsx @@ -16,6 +16,7 @@ import { FileText, Building2, AlertCircle, + XCircle, } from 'lucide-react'; import { toast } from 'react-toastify'; import { @@ -140,7 +141,18 @@ const BookingDetailPage: React.FC = () => { const handleCancelSuccess = async () => { if (booking) { - await fetchBookingDetails(booking.id); + // Refetch booking details after cancellation + const bookingId = validateBookingId(id); + if (bookingId) { + try { + const response = await getBookingById(bookingId); + if (response.success && response.data?.booking) { + setBooking(response.data.booking); + } + } catch (err) { + console.error('Error refetching booking:', err); + } + } } setShowCancelModal(false); }; @@ -170,8 +182,9 @@ const BookingDetailPage: React.FC = () => { const serviceUsages = (booking && typeof booking === 'object' && 'service_usages' in booking && Array.isArray((booking as { service_usages?: unknown[] }).service_usages)) ? (booking as { service_usages: unknown[] }).service_usages : ((booking && typeof booking === 'object' && 'services' in booking && Array.isArray((booking as { services?: unknown[] }).services)) ? (booking as { services: unknown[] }).services : []); if (Array.isArray(serviceUsages) && serviceUsages.length > 0) { - return serviceUsages.reduce((sum: number, su: { total_price?: number }) => { - return sum + (su.total_price || 0); + return serviceUsages.reduce((sum: number, su: unknown) => { + const serviceUsage = su as { total_price?: number }; + return sum + (serviceUsage.total_price || 0); }, 0); } return 0; @@ -192,7 +205,7 @@ const BookingDetailPage: React.FC = () => { const roomPricePerNight = useMemo(() => { if (!booking) return 0; - const roomTotal = booking.total_price - servicesTotal; + const roomTotal = booking.total_price - (servicesTotal as number); return nights > 0 ? roomTotal / nights : roomTotal; // eslint-disable-next-line react-hooks/exhaustive-deps }, [booking?.total_price, servicesTotal, nights]); @@ -476,8 +489,8 @@ const BookingDetailPage: React.FC = () => {

Payment Type:{' '} - {payment.payment_type === 'deposit' ? 'Deposit (20%)' : - payment.payment_type === 'remaining' ? 'Remaining Payment' : + {(payment as { payment_type?: string }).payment_type === 'deposit' ? 'Deposit (20%)' : + (payment as { payment_type?: string }).payment_type === 'remaining' ? 'Remaining Payment' : 'Full Payment'}

{payment.transaction_id && ( @@ -530,7 +543,7 @@ const BookingDetailPage: React.FC = () => { {} {serviceUsages.length > 0 && ( <> - {serviceUsages.map((serviceUsage: { id?: number; service_name?: string; name?: string; quantity?: number; price?: number; total?: number; total_price?: number }, index: number) => ( + {(serviceUsages as Array<{ id?: number; service_name?: string; name?: string; quantity?: number; price?: number; unit_price?: number; total?: number; total_price?: number }>).map((serviceUsage, index: number) => (

diff --git a/Frontend/src/pages/customer/BookingSuccessPage.tsx b/Frontend/src/pages/customer/BookingSuccessPage.tsx index 0e51e2a9..656e271b 100644 --- a/Frontend/src/pages/customer/BookingSuccessPage.tsx +++ b/Frontend/src/pages/customer/BookingSuccessPage.tsx @@ -131,7 +131,7 @@ const BookingSuccessPage: React.FC = () => { // If Stripe payment is pending, redirect to payment completion page if (bookingData.payment_method === PAYMENT_METHOD.STRIPE && bookingData.payments) { // Find any pending Stripe payment that needs completion - const pendingStripePayment = bookingData.payments.find( + const pendingStripePayment = (bookingData.payments as unknown as Payment[] | undefined)?.find( (p: Payment) => p.payment_method === PAYMENT_METHOD.STRIPE && p.payment_status === 'pending' // Payment record status (not booking payment_status) @@ -334,13 +334,13 @@ const BookingSuccessPage: React.FC = () => { // Check individual payment records for more granular status if (booking.payments && Array.isArray(booking.payments)) { // Calculate total from completed payment records - const totalPaid = booking.payments + const totalPaid = ((booking.payments as unknown as Payment[] | undefined) || []) .filter((p: Payment) => p.payment_status === 'completed') // Payment record status - .reduce((sum: number, p: Payment) => sum + parseFloat(p.amount?.toString() || '0'), 0); + .reduce((sum: number, p: Payment) => sum + (typeof p.amount === 'number' ? p.amount : parseFloat(String(p.amount || '0'))), 0); // For deposit bookings, check if deposit payment is completed if (booking.requires_deposit) { - const depositPayment = booking.payments.find( + const depositPayment = ((booking.payments as unknown as Payment[] | undefined))?.find( (p: Payment) => p.payment_type === PAYMENT_TYPE.DEPOSIT && p.payment_status === 'completed' ); if (depositPayment) { @@ -349,7 +349,7 @@ const BookingSuccessPage: React.FC = () => { } else { // For full payment bookings, check if total paid meets or exceeds booking price // Allow small floating point differences (0.01) for currency calculations - return totalPaid >= booking.total_price - 0.01; + return (totalPaid as number) >= booking.total_price - 0.01; } } @@ -663,7 +663,7 @@ const BookingSuccessPage: React.FC = () => { )} {} - {(booking.payment_method === PAYMENT_METHOD.CASH || booking.payment_method === PAYMENT_METHOD.BANK_TRANSFER) && ( + {((booking.payment_method as string) === PAYMENT_METHOD.CASH || (booking.payment_method as string) === PAYMENT_METHOD.BANK_TRANSFER) && (

{ If you cancel the booking, 20% of the total order value will be charged - {(booking.payment_method === PAYMENT_METHOD.CASH || booking.payment_method === PAYMENT_METHOD.BANK_TRANSFER) && ( + {((booking.payment_method as string) === PAYMENT_METHOD.CASH || (booking.payment_method as string) === PAYMENT_METHOD.BANK_TRANSFER) && (
  • Please transfer within 24 hours to secure your room diff --git a/Frontend/src/pages/customer/BoricaReturnPage.tsx b/Frontend/src/pages/customer/BoricaReturnPage.tsx index 9683610b..a1fb7663 100644 --- a/Frontend/src/pages/customer/BoricaReturnPage.tsx +++ b/Frontend/src/pages/customer/BoricaReturnPage.tsx @@ -3,6 +3,7 @@ import { useSearchParams, useNavigate } from 'react-router-dom'; import { confirmBoricaPayment } from '../../features/payments/services/paymentService'; import { toast } from 'react-toastify'; import { CheckCircle, XCircle, Loader2 } from 'lucide-react'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const BoricaReturnPage: React.FC = () => { const [searchParams] = useSearchParams(); diff --git a/Frontend/src/pages/customer/ComplaintPage.tsx b/Frontend/src/pages/customer/ComplaintPage.tsx index 5f17d036..c13eae72 100644 --- a/Frontend/src/pages/customer/ComplaintPage.tsx +++ b/Frontend/src/pages/customer/ComplaintPage.tsx @@ -5,9 +5,10 @@ import Loading from '../../shared/components/Loading'; import EmptyState from '../../shared/components/EmptyState'; import Pagination from '../../shared/components/Pagination'; import complaintService, { Complaint } from '../../features/guest_management/services/complaintService'; -import bookingService from '../../features/bookings/services/bookingService'; +import bookingService, { Booking } from '../../features/bookings/services/bookingService'; import { formatDate } from '../../shared/utils/format'; import { logger } from '../../shared/utils/logger'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const ComplaintPage: React.FC = () => { const [complaints, setComplaints] = useState([]); @@ -88,7 +89,7 @@ const ComplaintPage: React.FC = () => { return; } // Silently fail - bookings are optional, but log for debugging - logger.debug('Failed to fetch bookings for complaint form', error); + logger.debug('Failed to fetch bookings for complaint form', error as Record | undefined); } }; @@ -242,7 +243,7 @@ const ComplaintPage: React.FC = () => { const CreateComplaintModal: React.FC<{ bookings: Booking[]; onClose: () => void; - onSubmit: (data: { booking_id?: number; category: string; priority: string; title: string; description: string; attachments?: string[] }) => void; + onSubmit: (data: { booking_id?: number; room_id?: number; category: string; priority: string; title: string; description: string; attachments?: string[] }) => void; }> = ({ bookings, onClose, onSubmit }) => { const [formData, setFormData] = useState({ booking_id: '', diff --git a/Frontend/src/pages/customer/DashboardPage.tsx b/Frontend/src/pages/customer/DashboardPage.tsx index e76bab53..f14a7317 100644 --- a/Frontend/src/pages/customer/DashboardPage.tsx +++ b/Frontend/src/pages/customer/DashboardPage.tsx @@ -170,13 +170,13 @@ const DashboardPage: React.FC = () => { const fetchLoyalty = async () => { try { setLoadingLoyalty(true); - const response = await loyaltyService.getMyLoyalty(); + const response = await loyaltyService.getMyStatus(); if (response.status === 'success' && response.data) { setLoyaltyInfo({ available_points: response.data.available_points || 0, tier_name: response.data.tier?.name || 'Bronze', lifetime_points: response.data.lifetime_points || 0, - next_tier_points_needed: response.data.next_tier_points_needed, + next_tier_points_needed: response.data.points_needed_for_next_tier || response.data.next_tier?.points_needed || 0, }); } } catch (err: unknown) { @@ -189,7 +189,7 @@ const DashboardPage: React.FC = () => { }; if (!isAbortError(err)) { // Loyalty is optional, don't show error - logger.debug('Loyalty info not available', err); + logger.debug('Loyalty info not available', err as Record | undefined); } } finally { setLoadingLoyalty(false); diff --git a/Frontend/src/pages/customer/FullPaymentPage.tsx b/Frontend/src/pages/customer/FullPaymentPage.tsx index 10af79b2..2dcadb03 100644 --- a/Frontend/src/pages/customer/FullPaymentPage.tsx +++ b/Frontend/src/pages/customer/FullPaymentPage.tsx @@ -180,7 +180,7 @@ const FullPaymentPage: React.FC = () => { } else { // Fallback to payments from booking data if (bookingData.payments && bookingData.payments.length > 0) { - const stripePaymentFromBooking = bookingData.payments.find( + const stripePaymentFromBooking = (bookingData.payments as unknown as Payment[] | undefined)?.find( (p: Payment) => (p.payment_method === PAYMENT_METHOD.STRIPE || p.payment_method === PAYMENT_METHOD.CREDIT_CARD) && p.payment_status === 'pending' diff --git a/Frontend/src/pages/customer/GDPRDeletionConfirmPage.tsx b/Frontend/src/pages/customer/GDPRDeletionConfirmPage.tsx index a03d80da..c8849738 100644 --- a/Frontend/src/pages/customer/GDPRDeletionConfirmPage.tsx +++ b/Frontend/src/pages/customer/GDPRDeletionConfirmPage.tsx @@ -3,6 +3,7 @@ import { useSearchParams, useNavigate, Link } from 'react-router-dom'; import { CheckCircle, XCircle, AlertCircle, Loader2, Home, Shield } from 'lucide-react'; import { toast } from 'react-toastify'; import gdprService from '../../features/compliance/services/gdprService'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const GDPRDeletionConfirmPage: React.FC = () => { const [searchParams] = useSearchParams(); diff --git a/Frontend/src/pages/customer/GDPRPage.tsx b/Frontend/src/pages/customer/GDPRPage.tsx index 4c4da3dc..249e03dc 100644 --- a/Frontend/src/pages/customer/GDPRPage.tsx +++ b/Frontend/src/pages/customer/GDPRPage.tsx @@ -4,6 +4,7 @@ import gdprService, { GDPRRequest } from '../../features/compliance/services/gdp import { toast } from 'react-toastify'; import Loading from '../../shared/components/Loading'; import { formatDate } from '../../shared/utils/format'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const GDPRPage: React.FC = () => { const [requests, setRequests] = useState([]); diff --git a/Frontend/src/pages/customer/GroupBookingPage.tsx b/Frontend/src/pages/customer/GroupBookingPage.tsx index 27b91338..7c55aea7 100644 --- a/Frontend/src/pages/customer/GroupBookingPage.tsx +++ b/Frontend/src/pages/customer/GroupBookingPage.tsx @@ -7,6 +7,7 @@ import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurren import { formatDate } from '../../shared/utils/format'; import CreateGroupBookingModal from '../../features/hotel_services/components/CreateGroupBookingModal'; import { useNavigate } from 'react-router-dom'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const GroupBookingPage: React.FC = () => { const navigate = useNavigate(); diff --git a/Frontend/src/pages/customer/GuestRequestsPage.tsx b/Frontend/src/pages/customer/GuestRequestsPage.tsx index 26ad1b73..0f3f9731 100644 --- a/Frontend/src/pages/customer/GuestRequestsPage.tsx +++ b/Frontend/src/pages/customer/GuestRequestsPage.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; +// import { useNavigate } from 'react-router-dom'; // Unused import { Plus, Clock, @@ -22,10 +22,11 @@ import { logger } from '../../shared/utils/logger'; import Loading from '../../shared/components/Loading'; import EmptyState from '../../shared/components/EmptyState'; import useAuthStore from '../../store/useAuthStore'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const GuestRequestsPage: React.FC = () => { const { userInfo: _userInfo } = useAuthStore(); - const _navigate = useNavigate(); + // const _navigate = useNavigate(); // Unused const [loading, setLoading] = useState(true); const [requests, setRequests] = useState([]); const [bookings, setBookings] = useState([]); @@ -253,8 +254,10 @@ const GuestRequestsPage: React.FC = () => { ? "You need to be checked in to submit service requests. Please check in first or contact reception." : "Submit your first request to get started" } - actionLabel={bookings.length > 0 ? "Create Request" : undefined} - onAction={bookings.length > 0 ? () => setShowCreateModal(true) : undefined} + action={bookings.length > 0 ? { + label: "Create Request", + onClick: () => setShowCreateModal(true) + } : undefined} /> ) : (
    @@ -343,7 +346,7 @@ const GuestRequestsPage: React.FC = () => { booking_id: e.target.value, room_id: booking?.room_id?.toString() || '', }); - setSelectedBooking(booking); + setSelectedBooking(booking || null); }} className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--luxury-gold)]" required diff --git a/Frontend/src/pages/customer/InvoicePage.tsx b/Frontend/src/pages/customer/InvoicePage.tsx index 8d05a6e1..fa2fd412 100644 --- a/Frontend/src/pages/customer/InvoicePage.tsx +++ b/Frontend/src/pages/customer/InvoicePage.tsx @@ -8,6 +8,8 @@ import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurren import { formatDate } from '../../shared/utils/format'; import useAuthStore from '../../store/useAuthStore'; import { validateInvoiceId } from '../../shared/utils/routeValidation'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; +import { isAxiosError } from 'axios'; const InvoicePage: React.FC = () => { const { id } = useParams<{ id: string }>(); @@ -65,7 +67,7 @@ const InvoicePage: React.FC = () => { const invoiceData = response.data.invoice; // Validate ownership for customer role - if (userInfo?.role === 'customer' && invoiceData.booking?.user_id !== userInfo.id) { + if (userInfo?.role === 'customer' && invoiceData.user_id !== userInfo.id) { toast.error('You do not have permission to view this invoice'); navigate('/bookings'); setLoading(false); @@ -110,7 +112,7 @@ const InvoicePage: React.FC = () => { // If it's a 404 and we haven't retried yet, retry multiple times with increasing delays // This handles cases where invoice was just created and might not be immediately available - if (getUserFriendlyError(error) === 404 && retryCount < 3) { + if (isAxiosError(error) && error.response?.status === 404 && retryCount < 3) { // Wait with increasing delay (500ms, 1000ms, 2000ms) const delay = 500 * Math.pow(2, retryCount); setTimeout(() => { @@ -120,7 +122,7 @@ const InvoicePage: React.FC = () => { } // Handle invoice not found (404) after retries - show appropriate message - if (getUserFriendlyError(error) === 404) { + if (isAxiosError(error) && error.response?.status === 404) { handleInvoiceNotFound(); } else { // Other errors (network, server errors, etc.) - only show toast if not a validation error @@ -187,7 +189,7 @@ const InvoicePage: React.FC = () => { return ; } - if (!invoice && !loading) { + if (!invoice) { return (
    @@ -222,6 +224,18 @@ const InvoicePage: React.FC = () => { ); } + if (!invoice) { + return ( +
    +
    +
    +

    Loading invoice...

    +
    +
    +
    + ); + } + return (
    diff --git a/Frontend/src/pages/customer/LoyaltyPage.tsx b/Frontend/src/pages/customer/LoyaltyPage.tsx index 2c1ff5ca..e7a46dfb 100644 --- a/Frontend/src/pages/customer/LoyaltyPage.tsx +++ b/Frontend/src/pages/customer/LoyaltyPage.tsx @@ -22,6 +22,8 @@ import loyaltyService, { Referral } from '../../features/loyalty/services/loyaltyService'; import { formatDate } from '../../shared/utils/format'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; +import { isAxiosError } from 'axios'; type Tab = 'overview' | 'rewards' | 'history' | 'referrals'; @@ -100,16 +102,25 @@ const LoyaltyPage: React.FC = () => { setAnniversaryDate(response.data.anniversary_date); } } catch (error: unknown) { - const isAbortError = (e: unknown): boolean => { - return typeof e === 'object' && e !== null && (e as { name?: string; code?: string }).name === 'AbortError'; - return (e as { name?: string; code?: string }).code === 'ERR_CANCELED'; + const isAbortErrorFunc = (e: unknown): boolean => { + if (typeof e === 'object' && e !== null) { + const err = e as { name?: string; code?: string }; + return err.name === 'AbortError' || err.code === 'ERR_CANCELED'; + } + return false; }; + + if (isAbortErrorFunc(error)) { + return; + } + // Check if the error is about loyalty program being disabled // FastAPI returns detail in error.response.data.detail // The apiClient might transform it, so check both locations - const statusCode = getUserFriendlyError(error) || error.status; - const errorData = error.response?.data || {}; - const errorDetail = errorData.detail || errorData.message || ''; + const errorObj = error as { status?: number; response?: { data?: { detail?: unknown; message?: unknown } } }; + const statusCode = isAxiosError(error) ? error.response?.status : (errorObj.status || getUserFriendlyError(error)); + const errorData = errorObj.response?.data || {}; + const errorDetail = (typeof errorData === 'object' && errorData !== null && 'detail' in errorData ? errorData.detail : null) || (typeof errorData === 'object' && errorData !== null && 'message' in errorData ? errorData.message : null); const errorMessage = getUserFriendlyError(error) || ''; // Check if it's a 503 error (service unavailable) which indicates disabled diff --git a/Frontend/src/pages/customer/MyBookingsPage.tsx b/Frontend/src/pages/customer/MyBookingsPage.tsx index d6e4b80f..c84dc9cd 100644 --- a/Frontend/src/pages/customer/MyBookingsPage.tsx +++ b/Frontend/src/pages/customer/MyBookingsPage.tsx @@ -23,6 +23,7 @@ import EmptyState from '../../shared/components/EmptyState'; import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency'; import { parseDateLocal } from '../../shared/utils/format'; import { getBookingStatusConfig, canCancelBooking } from '../../shared/utils/bookingUtils'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const MyBookingsPage: React.FC = () => { const { isAuthenticated } = useAuthStore(); diff --git a/Frontend/src/pages/customer/PaymentConfirmationPage.tsx b/Frontend/src/pages/customer/PaymentConfirmationPage.tsx index 5bdc9db3..660b933a 100644 --- a/Frontend/src/pages/customer/PaymentConfirmationPage.tsx +++ b/Frontend/src/pages/customer/PaymentConfirmationPage.tsx @@ -30,6 +30,7 @@ import { validateBookingId } from '../../shared/utils/routeValidation'; import { validateAndHandleBookingOwnership } from '../../shared/utils/ownershipValidation'; import { PAYMENT_METHOD, PAYMENT_STATUS } from '../../shared/constants/bookingConstants'; import { useCompanySettings } from '../../shared/contexts/CompanySettingsContext'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const PaymentConfirmationPage: React.FC = () => { const { id } = useParams<{ id: string }>(); diff --git a/Frontend/src/pages/customer/ProfilePage.tsx b/Frontend/src/pages/customer/ProfilePage.tsx index 40da9593..9649e759 100644 --- a/Frontend/src/pages/customer/ProfilePage.tsx +++ b/Frontend/src/pages/customer/ProfilePage.tsx @@ -24,7 +24,9 @@ import { Trash2, Database, Smartphone, - Tablet + Tablet, + Eye, + EyeOff } from 'lucide-react'; import { toast } from 'react-toastify'; import authService from '../../features/auth/services/authService'; @@ -38,7 +40,9 @@ import { useGlobalLoading } from '../../shared/contexts/LoadingContext'; import { normalizeImageUrl } from '../../shared/utils/imageUtils'; import { formatDate } from '../../shared/utils/format'; import { UserSession } from '../../features/auth/services/sessionService'; -import { useNavigate } from 'react-router-dom'; +// import { useNavigate } from 'react-router-dom'; // Unused +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; +import { isAxiosError } from 'axios'; const profileValidationSchema = yup.object().shape({ name: yup @@ -83,7 +87,7 @@ type PasswordFormData = yup.InferType; const ProfilePage: React.FC = () => { const { userInfo, setUser } = useAuthStore(); const { setLoading } = useGlobalLoading(); - const _navigate = useNavigate(); + // const _navigate = useNavigate(); // Unused const [activeTab, setActiveTab] = useState<'profile' | 'password' | 'mfa' | 'sessions' | 'gdpr'>('profile'); const [avatarPreview, setAvatarPreview] = useState(null); const [avatarError, setAvatarError] = useState(false); @@ -105,8 +109,8 @@ const ProfilePage: React.FC = () => { const [showBackupCodes, setShowBackupCodes] = useState(null); const [showMfaSecret, setShowMfaSecret] = useState(false); const mfaAbortControllerRef = useRef(null); - const _sessionsAbortControllerRef = useRef(null); - const _gdprAbortControllerRef = useRef(null); + // const _sessionsAbortControllerRef = useRef(null); // Unused + // const _gdprAbortControllerRef = useRef(null); // Unused const fetchProfile = async () => { @@ -241,8 +245,16 @@ const ProfilePage: React.FC = () => { if (response.status === 'success' || response.success) { const updatedUser = response.data?.user || response.data; - if (updatedUser) { - setUser(updatedUser); + if (updatedUser && typeof updatedUser === 'object' && 'id' in updatedUser) { + setUser({ + id: (updatedUser as { id: number }).id, + name: ((updatedUser as { name?: string; full_name?: string }).name || (updatedUser as { name?: string; full_name?: string }).full_name || '') as string, + email: (updatedUser as { email: string }).email, + phone: ((updatedUser as { phone?: string; phone_number?: string }).phone || (updatedUser as { phone?: string; phone_number?: string }).phone_number) as string | undefined, + avatar: (updatedUser as { avatar?: string }).avatar, + role: (updatedUser as { role: string }).role, + createdAt: ((updatedUser as { createdAt?: string; created_at?: string }).createdAt || (updatedUser as { createdAt?: string; created_at?: string }).created_at) as string | undefined, + }); toast.success('Profile updated successfully!'); refetchProfile(); } @@ -1158,7 +1170,7 @@ const SessionsTab: React.FC = () => { fetchSessions(); } } catch (error: unknown) { - if (getUserFriendlyError(error) === 401) { + if (isAxiosError(error) && error.response?.status === 401) { toast.warning('Your session has been revoked. You will be logged out.'); setTimeout(() => { window.location.href = '/'; @@ -1184,7 +1196,7 @@ const SessionsTab: React.FC = () => { fetchSessions(); } } catch (error: unknown) { - if (getUserFriendlyError(error) === 401) { + if (isAxiosError(error) && error.response?.status === 401) { toast.warning('All sessions have been revoked. You will be logged out.'); setTimeout(() => { window.location.href = '/'; diff --git a/Frontend/src/pages/customer/RoomDetailPage.tsx b/Frontend/src/pages/customer/RoomDetailPage.tsx index c289f338..73073d5e 100644 --- a/Frontend/src/pages/customer/RoomDetailPage.tsx +++ b/Frontend/src/pages/customer/RoomDetailPage.tsx @@ -23,6 +23,7 @@ import { toast } from 'react-toastify'; import { logger } from '../../shared/utils/logger'; import { useTheme } from '../../shared/contexts/ThemeContext'; import { getThemeBackgroundClasses, getThemeTextClasses, getThemeCardClasses } from '../../shared/utils/themeUtils'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const RoomDetailPage: React.FC = () => { const { theme } = useTheme(); @@ -72,7 +73,7 @@ const RoomDetailPage: React.FC = () => { setError(null); const response = await getRoomByNumber(roomNumber); - if (response.success || response.status === 'success') { + if (response.success) { if (response.data && response.data.room) { const fetchedRoom = response.data.room; diff --git a/Frontend/src/pages/customer/RoomListPage.tsx b/Frontend/src/pages/customer/RoomListPage.tsx index f39bfb08..3b961f0b 100644 --- a/Frontend/src/pages/customer/RoomListPage.tsx +++ b/Frontend/src/pages/customer/RoomListPage.tsx @@ -30,7 +30,7 @@ const RoomListPage: React.FC = () => { totalPages: 1, }); const abortControllerRef = useRef(null); - const [activePromotion, setActivePromotion] = useState<{ id: number; title: string; description?: string; discount_percentage?: number; valid_until?: string } | null>(null); + const [activePromotion, setActivePromotion] = useState<{ id?: number; code?: string; title?: string; name?: string; description?: string; discount?: string; discount_percentage?: number; discount_value?: number; discount_type?: 'percentage' | 'fixed'; valid_until?: string } | null>(null); const [showPromotionBanner, setShowPromotionBanner] = useState(false); // Check for active promotion from URL or sessionStorage @@ -173,12 +173,12 @@ const RoomListPage: React.FC = () => {
    - Active Promotion: {activePromotion.code || activePromotion.title} + Active Promotion: {activePromotion.code || activePromotion.title || activePromotion.name || 'Special Offer'}
    - {activePromotion.discount && ( + {(activePromotion.discount || activePromotion.discount_value) && (

    - {activePromotion.discount} - {activePromotion.description || 'Valid on bookings'} + {activePromotion.discount || (activePromotion.discount_type === 'percentage' ? `${activePromotion.discount_value}%` : `$${activePromotion.discount_value}`)} - {activePromotion.description || 'Valid on bookings'}

    )}

    @@ -388,6 +388,9 @@ const RoomListPage: React.FC = () => { { + setPagination({ ...pagination, page }); + }} />

    )} diff --git a/Frontend/src/pages/customer/SearchResultsPage.tsx b/Frontend/src/pages/customer/SearchResultsPage.tsx index 5b4e9b61..2280c568 100644 --- a/Frontend/src/pages/customer/SearchResultsPage.tsx +++ b/Frontend/src/pages/customer/SearchResultsPage.tsx @@ -301,6 +301,7 @@ const SearchResultsPage: React.FC = () => { setPagination({ ...pagination, page })} /> ) : ( diff --git a/Frontend/src/pages/customer/SessionManagementPage.tsx b/Frontend/src/pages/customer/SessionManagementPage.tsx index a2da042d..64c64851 100644 --- a/Frontend/src/pages/customer/SessionManagementPage.tsx +++ b/Frontend/src/pages/customer/SessionManagementPage.tsx @@ -4,6 +4,8 @@ import sessionService, { UserSession } from '../../features/auth/services/sessio import { toast } from 'react-toastify'; import Loading from '../../shared/components/Loading'; import { formatDate } from '../../shared/utils/format'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; +import { isAxiosError } from 'axios'; const SessionManagementPage: React.FC = () => { const [sessions, setSessions] = useState([]); @@ -71,7 +73,7 @@ const SessionManagementPage: React.FC = () => { } } catch (error: unknown) { // Check if it's an unauthorized error (session was revoked) - if (getUserFriendlyError(error) === 401) { + if (isAxiosError(error) && error.response?.status === 401) { toast.warning('Your session has been revoked. You will be logged out.'); setTimeout(() => { window.location.href = '/'; @@ -102,7 +104,7 @@ const SessionManagementPage: React.FC = () => { } } catch (error: unknown) { // Check if it's an unauthorized error (sessions were revoked) - if (getUserFriendlyError(error) === 401) { + if (isAxiosError(error) && error.response?.status === 401) { toast.warning('All sessions have been revoked. You will be logged out.'); setTimeout(() => { window.location.href = '/'; diff --git a/Frontend/src/pages/housekeeping/DashboardPage.tsx b/Frontend/src/pages/housekeeping/DashboardPage.tsx index a0313caa..28ed2f39 100644 --- a/Frontend/src/pages/housekeeping/DashboardPage.tsx +++ b/Frontend/src/pages/housekeeping/DashboardPage.tsx @@ -31,6 +31,7 @@ import advancedRoomService, { HousekeepingTask, ChecklistItem, RoomInspection, I import roomService, { Room } from '../../features/rooms/services/roomService'; import { logger } from '../../shared/utils/logger'; import useAuthStore from '../../store/useAuthStore'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; // Luxury Task Detail Modal Component interface TaskModalProps { @@ -43,7 +44,7 @@ interface TaskModalProps { onUploadPhoto: (task: HousekeepingTask, file: File) => Promise; onDeletePhoto: (task: HousekeepingTask, photoUrl: string) => Promise; onReportIssue: (task: HousekeepingTask, issueData: { title: string; description: string; priority: string }) => Promise; - onCreateInspection?: (task: HousekeepingTask) => void; + onCreateInspection?: ((task: HousekeepingTask) => void) | undefined; isUpdating: boolean; } @@ -57,7 +58,7 @@ const TaskDetailModal: React.FC = ({ onUploadPhoto, onDeletePhoto, onReportIssue, - _onCreateInspection, + onCreateInspection: _onCreateInspection, isUpdating, }) => { const fileInputRef = React.useRef(null); @@ -1958,7 +1959,7 @@ const HousekeepingDashboardPage: React.FC = () => { onUploadPhoto={handleUploadPhoto} onDeletePhoto={handleDeletePhoto} onReportIssue={handleReportIssue} - onCreateInspection={handleCreateInspectionFromTask} + onCreateInspection={handleCreateInspectionFromTask as ((task: HousekeepingTask) => void) | undefined} isUpdating={selectedTask && selectedTask.id ? updatingTasks.has(selectedTask.id) : false} /> diff --git a/Frontend/src/pages/housekeeping/ProfilePage.tsx b/Frontend/src/pages/housekeeping/ProfilePage.tsx index 729ab0ec..9eafde05 100644 --- a/Frontend/src/pages/housekeeping/ProfilePage.tsx +++ b/Frontend/src/pages/housekeeping/ProfilePage.tsx @@ -34,6 +34,8 @@ import { useGlobalLoading } from '../../shared/contexts/LoadingContext'; import { normalizeImageUrl } from '../../shared/utils/imageUtils'; import { formatDate } from '../../shared/utils/format'; import { UserSession } from '../../features/auth/services/sessionService'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; +import { isAxiosError } from 'axios'; const profileValidationSchema = yup.object().shape({ name: yup @@ -217,8 +219,16 @@ const HousekeepingProfilePage: React.FC = () => { if (response.status === 'success' || response.success) { const updatedUser = response.data?.user || response.data; - if (updatedUser) { - setUser(updatedUser); + if (updatedUser && typeof updatedUser === 'object' && 'id' in updatedUser) { + setUser({ + id: (updatedUser as { id: number }).id, + name: ((updatedUser as { name?: string; full_name?: string }).name || (updatedUser as { name?: string; full_name?: string }).full_name || '') as string, + email: (updatedUser as { email: string }).email, + phone: ((updatedUser as { phone?: string; phone_number?: string }).phone || (updatedUser as { phone?: string; phone_number?: string }).phone_number) as string | undefined, + avatar: (updatedUser as { avatar?: string }).avatar, + role: (updatedUser as { role: string }).role, + createdAt: ((updatedUser as { createdAt?: string; created_at?: string }).createdAt || (updatedUser as { createdAt?: string; created_at?: string }).created_at) as string | undefined, + }); toast.success('Profile updated successfully!'); refetchProfile(); } @@ -1044,7 +1054,7 @@ const SessionsTab: React.FC = () => { fetchSessions(); } } catch (error: unknown) { - if (getUserFriendlyError(error) === 401) { + if (isAxiosError(error) && error.response?.status === 401) { toast.warning('Your session has been revoked. You will be logged out.'); setTimeout(() => { window.location.href = '/'; }, 2000); } else { @@ -1065,7 +1075,7 @@ const SessionsTab: React.FC = () => { fetchSessions(); } } catch (error: unknown) { - if (getUserFriendlyError(error) === 401) { + if (isAxiosError(error) && error.response?.status === 401) { toast.warning('All sessions have been revoked. You will be logged out.'); setTimeout(() => { window.location.href = '/'; }, 2000); } else { diff --git a/Frontend/src/pages/housekeeping/ShiftViewPage.tsx b/Frontend/src/pages/housekeeping/ShiftViewPage.tsx index 3d9df16b..42808856 100644 --- a/Frontend/src/pages/housekeeping/ShiftViewPage.tsx +++ b/Frontend/src/pages/housekeeping/ShiftViewPage.tsx @@ -4,6 +4,7 @@ import staffShiftService, { StaffShift, StaffTask } from '../../features/staffSh import { toast } from 'react-toastify'; import Loading from '../../shared/components/Loading'; import { formatDate } from '../../shared/utils/format'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const HousekeepingShiftViewPage: React.FC = () => { const [shifts, setShifts] = useState([]); diff --git a/Frontend/src/pages/housekeeping/TasksPage.tsx b/Frontend/src/pages/housekeeping/TasksPage.tsx index 1c60a92c..c12e2bb4 100644 --- a/Frontend/src/pages/housekeeping/TasksPage.tsx +++ b/Frontend/src/pages/housekeeping/TasksPage.tsx @@ -43,7 +43,7 @@ const HousekeepingTasksPage: React.FC = () => { pending: tasks.filter((t: { status?: string }) => t.status === 'pending').length, in_progress: tasks.filter((t: { status?: string }) => t.status === 'in_progress').length, completed: tasks.filter((t: { status?: string }) => t.status === 'completed').length, - today: tasks.filter((t: { status?: string; due_date?: string }) => { + today: tasks.filter((t: { status?: string; due_date?: string; scheduled_time?: string }) => { if (!t.scheduled_time) return false; const taskDate = new Date(t.scheduled_time).toISOString().split('T')[0]; return taskDate === today; diff --git a/Frontend/src/pages/staff/AdvancedRoomManagementPage.tsx b/Frontend/src/pages/staff/AdvancedRoomManagementPage.tsx index 77dfea32..03b16cd7 100644 --- a/Frontend/src/pages/staff/AdvancedRoomManagementPage.tsx +++ b/Frontend/src/pages/staff/AdvancedRoomManagementPage.tsx @@ -31,6 +31,7 @@ import MaintenanceManagement from '../../features/hotel_services/components/Main import HousekeepingManagement from '../../features/hotel_services/components/HousekeepingManagement'; import InspectionManagement from '../../features/hotel_services/components/InspectionManagement'; import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; type Tab = 'status-board' | 'room-assignment' | 'maintenance' | 'housekeeping' | 'inspections'; @@ -230,7 +231,8 @@ const AdvancedRoomManagementPage: React.FC = () => { // Fetch available rooms for the booking dates try { setLoadingAvailableRooms(true); - const _response = await roomService.searchAvailableRooms({ + // const _response = await roomService.searchAvailableRooms({ + await roomService.searchAvailableRooms({ from: booking.check_in_date, to: booking.check_out_date, limit: 100, @@ -241,7 +243,7 @@ const AdvancedRoomManagementPage: React.FC = () => { const allRooms = statusBoardResponse.data.rooms || []; // Filter to show only available rooms that match the booking's room type or better - const availableRooms = allRooms.filter(room => { + const availableRooms = allRooms.filter((room: { status: string; room_type?: string }) => { const isAvailable = room.status === 'available'; const matchesType = !booking.room?.room_type || room.room_type === booking.room.room_type.name || @@ -252,7 +254,7 @@ const AdvancedRoomManagementPage: React.FC = () => { setAvailableRoomsForAssignment(availableRooms); // Get upgrade suggestions (better room types) - const upgradeRooms = allRooms.filter(room => { + const upgradeRooms = allRooms.filter((room: { status: string; room_type?: string }) => { return room.status === 'available' && room.room_type && room.room_type !== booking.room?.room_type?.name; diff --git a/Frontend/src/pages/staff/AnalyticsDashboardPage.tsx b/Frontend/src/pages/staff/AnalyticsDashboardPage.tsx index 0d0a742f..4a25badc 100644 --- a/Frontend/src/pages/staff/AnalyticsDashboardPage.tsx +++ b/Frontend/src/pages/staff/AnalyticsDashboardPage.tsx @@ -39,6 +39,7 @@ import reportService, { ReportData } from '../../features/analytics/services/rep import reviewService, { Review } from '../../features/reviews/services/reviewService'; import { auditService, AuditLog, AuditLogFilters } from '../../features/analytics/services/auditService'; import { formatDate } from '../../shared/utils/format'; +import { logger } from '../../shared/utils/logger'; import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency'; import analyticsService, { ComprehensiveAnalyticsData, @@ -60,6 +61,7 @@ import analyticsService, { import { SimpleBarChart, SimpleLineChart, SimplePieChart, KPICard } from '../../features/analytics/components/SimpleChart'; import { exportData } from '../../shared/utils/exportUtils'; import CustomReportBuilder from '../../features/analytics/components/CustomReportBuilder'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; type AnalyticsTab = 'overview' | 'reports' | 'revenue' | 'operational' | 'guest' | 'financial' | 'audit-logs' | 'reviews'; diff --git a/Frontend/src/pages/staff/BookingManagementPage.tsx b/Frontend/src/pages/staff/BookingManagementPage.tsx index 4d5b9abc..6b61b0c9 100644 --- a/Frontend/src/pages/staff/BookingManagementPage.tsx +++ b/Frontend/src/pages/staff/BookingManagementPage.tsx @@ -11,6 +11,7 @@ import { useNavigate } from 'react-router-dom'; import CreateBookingModal from '../../features/hotel_services/components/CreateBookingModal'; import { logger } from '../../shared/utils/logger'; import { validateBookingId } from '../../shared/utils/routeValidation'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const BookingManagementPage: React.FC = () => { const { formatCurrency } = useFormatCurrency(); @@ -91,7 +92,7 @@ const BookingManagementPage: React.FC = () => { const handleUpdateStatus = async (id: number, status: string) => { try { setUpdatingBookingId(id); - await bookingService.updateBooking(id, { status } as { status: string }); + await bookingService.updateBooking(id, { status: status as Booking['status'] }); toast.success('Status updated successfully'); await fetchBookings(); } catch (error: unknown) { @@ -119,7 +120,7 @@ const BookingManagementPage: React.FC = () => { const handleCreateInvoice = async (bookingId: number) => { try { // Validate bookingId before proceeding - const validatedId = validateBookingId(bookingId); + const validatedId = validateBookingId(bookingId.toString()); if (!validatedId) { toast.error('Invalid booking ID'); return; @@ -549,11 +550,11 @@ const BookingManagementPage: React.FC = () => {

    {service.service_name || service.name || 'Service'}

    - {formatCurrency(service.unit_price || service.price || 0)} × {service.quantity || 1} + {formatCurrency((service as { unit_price?: number; price?: number }).unit_price || (service as { unit_price?: number; price?: number }).price || 0)} × {service.quantity || 1}

    - {formatCurrency(service.total_price || (service.unit_price || service.price || 0) * (service.quantity || 1))} + {formatCurrency((service as { total_price?: number; unit_price?: number; price?: number }).total_price || ((service as { unit_price?: number; price?: number }).unit_price || (service as { unit_price?: number; price?: number }).price || 0) * (service.quantity || 1))}

    ))} @@ -597,13 +598,13 @@ const BookingManagementPage: React.FC = () => {

    - {payment.payment_status === 'completed' || payment.payment_status === 'paid' ? 'Paid' : payment.payment_status || 'Pending'} + {payment.payment_status === 'completed' ? 'Paid' : payment.payment_status || 'Pending'}
    {payment.transaction_id && ( diff --git a/Frontend/src/pages/staff/ChatManagementPage.tsx b/Frontend/src/pages/staff/ChatManagementPage.tsx index dd020a84..adc3f625 100644 --- a/Frontend/src/pages/staff/ChatManagementPage.tsx +++ b/Frontend/src/pages/staff/ChatManagementPage.tsx @@ -8,6 +8,7 @@ import { formatDate } from '../../shared/utils/format'; import useAuthStore from '../../store/useAuthStore'; import { useChatNotifications } from '../../features/notifications/contexts/ChatNotificationContext'; import { logger } from '../../shared/utils/logger'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const ChatManagementPage: React.FC = () => { const [chats, setChats] = useState([]); diff --git a/Frontend/src/pages/staff/GuestCommunicationPage.tsx b/Frontend/src/pages/staff/GuestCommunicationPage.tsx index 907d9f33..eedb1bab 100644 --- a/Frontend/src/pages/staff/GuestCommunicationPage.tsx +++ b/Frontend/src/pages/staff/GuestCommunicationPage.tsx @@ -22,6 +22,7 @@ import bookingService, { Booking } from '../../features/bookings/services/bookin import userService from '../../features/auth/services/userService'; import apiClient from '../../shared/services/apiClient'; import Pagination from '../../shared/components/Pagination'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; interface CommunicationTemplate { id: number; @@ -55,8 +56,8 @@ const GuestCommunicationPage: React.FC = () => { // Send communication state const [guestSearch, setGuestSearch] = useState(''); - const [guestSearchResults, setGuestSearchResults] = useState>([]); - const [selectedGuest, setSelectedGuest] = useState<{ id: number; name: string; email?: string; phone?: string } | null>(null); + const [guestSearchResults, setGuestSearchResults] = useState>([]); + const [selectedGuest, setSelectedGuest] = useState<{ id: number; name?: string; full_name?: string; email?: string; phone?: string } | null>(null); const [selectedBooking, setSelectedBooking] = useState(null); const [communicationForm, setCommunicationForm] = useState({ communication_type: 'email', @@ -109,14 +110,26 @@ const GuestCommunicationPage: React.FC = () => { role: 'customer', limit: 10, }); - setGuestSearchResults(response.data.users || []); + setGuestSearchResults((response.data.users || []).map((u: { id: number; name?: string; full_name?: string; email?: string; phone?: string }) => ({ + id: u.id, + name: u.name || u.full_name, + full_name: u.full_name || u.name, + email: u.email, + phone: u.phone, + }))); } catch (error) { logger.error('Error searching guests', error); } }; - const handleSelectGuest = async (guest: { id: number; full_name: string; email?: string; phone?: string }) => { - setSelectedGuest(guest); + const handleSelectGuest = async (guest: { id: number; name?: string; full_name?: string; email?: string; phone?: string }) => { + setSelectedGuest({ + id: guest.id, + name: guest.name || guest.full_name, + full_name: guest.full_name || guest.name, + email: guest.email, + phone: guest.phone, + }); setGuestSearch(''); setGuestSearchResults([]); @@ -196,7 +209,7 @@ const GuestCommunicationPage: React.FC = () => { const fetchCommunications = async () => { try { setLoading(true); - const params: { page: number; limit: number; guest_id?: number } = { + const params: { page: number; limit: number; guest_id?: number; communication_type?: string; direction?: string } = { page: historyPage, limit: 20, }; diff --git a/Frontend/src/pages/staff/GuestProfilePage.tsx b/Frontend/src/pages/staff/GuestProfilePage.tsx index da415903..f5a122a3 100644 --- a/Frontend/src/pages/staff/GuestProfilePage.tsx +++ b/Frontend/src/pages/staff/GuestProfilePage.tsx @@ -22,6 +22,7 @@ import Loading from '../../shared/components/Loading'; import Pagination from '../../shared/components/Pagination'; import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurrency'; import { logger } from '../../shared/utils/logger'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; type TabType = 'list' | 'profile'; @@ -108,7 +109,7 @@ const GuestProfilePage: React.FC = () => { setCurrentPage(1); }; - const handleFilterChange = (key: keyof GuestSearchParams, value: string | number | undefined) => { + const handleFilterChange = (key: keyof GuestSearchParams, value: string | number | boolean | undefined) => { setFilters({ ...filters, [key]: value, page: 1 }); setCurrentPage(1); }; diff --git a/Frontend/src/pages/staff/GuestRequestManagementPage.tsx b/Frontend/src/pages/staff/GuestRequestManagementPage.tsx index a5f3c94b..a99880c8 100644 --- a/Frontend/src/pages/staff/GuestRequestManagementPage.tsx +++ b/Frontend/src/pages/staff/GuestRequestManagementPage.tsx @@ -27,6 +27,7 @@ import Loading from '../../shared/components/Loading'; import EmptyState from '../../shared/components/EmptyState'; import useAuthStore from '../../store/useAuthStore'; import Pagination from '../../shared/components/Pagination'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const GuestRequestManagementPage: React.FC = () => { const { userInfo } = useAuthStore(); diff --git a/Frontend/src/pages/staff/IncidentComplaintManagementPage.tsx b/Frontend/src/pages/staff/IncidentComplaintManagementPage.tsx index 5361e1ec..e3205f3e 100644 --- a/Frontend/src/pages/staff/IncidentComplaintManagementPage.tsx +++ b/Frontend/src/pages/staff/IncidentComplaintManagementPage.tsx @@ -19,6 +19,7 @@ import { logger } from '../../shared/utils/logger'; import complaintService, { Complaint, ComplaintUpdate } from '../../features/complaints/services/complaintService'; import userService from '../../features/auth/services/userService'; import Pagination from '../../shared/components/Pagination'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const IncidentComplaintManagementPage: React.FC = () => { const [loading, setLoading] = useState(true); @@ -98,7 +99,11 @@ const IncidentComplaintManagementPage: React.FC = () => { try { const response = await userService.getUsers({ role: 'staff', limit: 100 }); if (response.data?.users) { - setStaffMembers(response.data.users); + setStaffMembers((response.data.users as Array<{ id: number; name?: string; full_name?: string; email?: string }>).map(u => ({ + id: u.id, + name: u.name || u.full_name || '', + email: u.email, + }))); } } catch (error) { logger.error('Failed to fetch staff members', error); @@ -423,7 +428,7 @@ const IncidentComplaintManagementPage: React.FC = () => { {staffMembers.map((staff) => ( ))} @@ -704,7 +709,7 @@ const IncidentComplaintManagementPage: React.FC = () => { {staffMembers.map((staff) => ( ))} diff --git a/Frontend/src/pages/staff/InventoryViewPage.tsx b/Frontend/src/pages/staff/InventoryViewPage.tsx index 32943641..3aa18067 100644 --- a/Frontend/src/pages/staff/InventoryViewPage.tsx +++ b/Frontend/src/pages/staff/InventoryViewPage.tsx @@ -4,6 +4,7 @@ import inventoryService, { InventoryItem } from '../../features/inventory/servic import { toast } from 'react-toastify'; import Loading from '../../shared/components/Loading'; import Pagination from '../../shared/components/Pagination'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const InventoryViewPage: React.FC = () => { const [items, setItems] = useState([]); @@ -76,7 +77,7 @@ const InventoryViewPage: React.FC = () => { const fetchItems = async () => { try { setLoading(true); - const params: { page: number; limit: number; search?: string; category?: string; status?: string } = { + const params: { page: number; limit: number; search?: string; category?: string; status?: string; low_stock?: boolean } = { page: currentPage, limit: itemsPerPage, }; @@ -445,8 +446,8 @@ const InventoryViewPage: React.FC = () => {

    Supplier

    {viewingItem.supplier}

    - {viewingItem.supplier_contact && ( -

    {viewingItem.supplier_contact}

    + {(viewingItem as { supplier_contact?: string }).supplier_contact && ( +

    {(viewingItem as { supplier_contact?: string }).supplier_contact}

    )}
    )} diff --git a/Frontend/src/pages/staff/LoyaltyManagementPage.tsx b/Frontend/src/pages/staff/LoyaltyManagementPage.tsx index aed80dae..8b44f118 100644 --- a/Frontend/src/pages/staff/LoyaltyManagementPage.tsx +++ b/Frontend/src/pages/staff/LoyaltyManagementPage.tsx @@ -20,6 +20,7 @@ import ConfirmationDialog from '../../shared/components/ConfirmationDialog'; import loyaltyService, { LoyaltyTier, LoyaltyReward } from '../../features/loyalty/services/loyaltyService'; import Pagination from '../../shared/components/Pagination'; import { logger } from '../../shared/utils/logger'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; type Tab = 'users' | 'tiers' | 'rewards'; diff --git a/Frontend/src/pages/staff/PaymentManagementPage.tsx b/Frontend/src/pages/staff/PaymentManagementPage.tsx index 32f2ed5d..1b2fdd82 100644 --- a/Frontend/src/pages/staff/PaymentManagementPage.tsx +++ b/Frontend/src/pages/staff/PaymentManagementPage.tsx @@ -10,6 +10,7 @@ import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurren import { formatDate } from '../../shared/utils/format'; import { logger } from '../../shared/utils/logger'; import { getPaymentStatusColor, getPaymentMethodLabel } from '../../shared/utils/paymentUtils'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; const PaymentManagementPage: React.FC = () => { const { formatCurrency } = useFormatCurrency(); diff --git a/Frontend/src/pages/staff/ProfilePage.tsx b/Frontend/src/pages/staff/ProfilePage.tsx index b279d30d..8521dc5c 100644 --- a/Frontend/src/pages/staff/ProfilePage.tsx +++ b/Frontend/src/pages/staff/ProfilePage.tsx @@ -24,7 +24,9 @@ import { Trash2, Database, Smartphone, - Tablet + Tablet, + Eye, + EyeOff } from 'lucide-react'; import { toast } from 'react-toastify'; import authService from '../../features/auth/services/authService'; @@ -38,7 +40,9 @@ import { useGlobalLoading } from '../../shared/contexts/LoadingContext'; import { normalizeImageUrl } from '../../shared/utils/imageUtils'; import { formatDate } from '../../shared/utils/format'; import { UserSession } from '../../features/auth/services/sessionService'; -import { useNavigate } from 'react-router-dom'; +// import { useNavigate } from 'react-router-dom'; // Unused +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; +import { isAxiosError } from 'axios'; const profileValidationSchema = yup.object().shape({ name: yup @@ -83,7 +87,7 @@ type PasswordFormData = yup.InferType; const ProfilePage: React.FC = () => { const { userInfo, setUser } = useAuthStore(); const { setLoading } = useGlobalLoading(); - const _navigate = useNavigate(); + // const _navigate = useNavigate(); // Unused const [activeTab, setActiveTab] = useState<'profile' | 'password' | 'mfa' | 'sessions' | 'gdpr'>('profile'); const [avatarPreview, setAvatarPreview] = useState(null); const [avatarError, setAvatarError] = useState(false); @@ -105,8 +109,8 @@ const ProfilePage: React.FC = () => { const [showBackupCodes, setShowBackupCodes] = useState(null); const [showMfaSecret, setShowMfaSecret] = useState(false); const mfaAbortControllerRef = useRef(null); - const _sessionsAbortControllerRef = useRef(null); - const _gdprAbortControllerRef = useRef(null); + // const _sessionsAbortControllerRef = useRef(null); // Unused + // const _gdprAbortControllerRef = useRef(null); // Unused const fetchProfile = async () => { @@ -241,8 +245,16 @@ const ProfilePage: React.FC = () => { if (response.status === 'success' || response.success) { const updatedUser = response.data?.user || response.data; - if (updatedUser) { - setUser(updatedUser); + if (updatedUser && typeof updatedUser === 'object' && 'id' in updatedUser) { + setUser({ + id: (updatedUser as { id: number }).id, + name: ((updatedUser as { name?: string; full_name?: string }).name || (updatedUser as { name?: string; full_name?: string }).full_name || '') as string, + email: (updatedUser as { email: string }).email, + phone: ((updatedUser as { phone?: string; phone_number?: string }).phone || (updatedUser as { phone?: string; phone_number?: string }).phone_number) as string | undefined, + avatar: (updatedUser as { avatar?: string }).avatar, + role: (updatedUser as { role: string }).role, + createdAt: ((updatedUser as { createdAt?: string; created_at?: string }).createdAt || (updatedUser as { createdAt?: string; created_at?: string }).created_at) as string | undefined, + }); toast.success('Profile updated successfully!'); refetchProfile(); } @@ -1158,7 +1170,7 @@ const SessionsTab: React.FC = () => { fetchSessions(); } } catch (error: unknown) { - if (getUserFriendlyError(error) === 401) { + if (isAxiosError(error) && error.response?.status === 401) { toast.warning('Your session has been revoked. You will be logged out.'); setTimeout(() => { window.location.href = '/'; @@ -1184,7 +1196,7 @@ const SessionsTab: React.FC = () => { fetchSessions(); } } catch (error: unknown) { - if (getUserFriendlyError(error) === 401) { + if (isAxiosError(error) && error.response?.status === 401) { toast.warning('All sessions have been revoked. You will be logged out.'); setTimeout(() => { window.location.href = '/'; diff --git a/Frontend/src/pages/staff/ReceptionDashboardPage.tsx b/Frontend/src/pages/staff/ReceptionDashboardPage.tsx index 6bca2ce2..5f0c6965 100644 --- a/Frontend/src/pages/staff/ReceptionDashboardPage.tsx +++ b/Frontend/src/pages/staff/ReceptionDashboardPage.tsx @@ -37,6 +37,7 @@ import { useFormatCurrency } from '../../features/payments/hooks/useFormatCurren import { parseDateLocal } from '../../shared/utils/format'; import CreateBookingModal from '../../features/hotel_services/components/CreateBookingModal'; import { logger } from '../../shared/utils/logger'; +import { getUserFriendlyError } from '../../shared/utils/errorSanitizer'; type ReceptionTab = 'overview' | 'check-in' | 'check-out' | 'walk-in' | 'bookings' | 'rooms' | 'services'; @@ -339,8 +340,8 @@ const ReceptionDashboardPage: React.FC = () => { setCheckOutLoading(true); await bookingService.updateBooking(checkOutBooking.id, { - status: 'checked_out', - } as { status: string }); + status: 'checked_out' as Booking['status'], + }); toast.success('Check-out successful'); setShowInvoice(true); @@ -532,7 +533,7 @@ const ReceptionDashboardPage: React.FC = () => { const handleUpdateBookingStatus = async (id: number, status: string) => { try { setUpdatingBookingId(id); - await bookingService.updateBooking(id, { status } as { status: string }); + await bookingService.updateBooking(id, { status: status as Booking['status'] }); toast.success('Status updated successfully'); await fetchBookings(); } catch (error: unknown) { @@ -777,7 +778,7 @@ const ReceptionDashboardPage: React.FC = () => { room_number: response.data.room.room_number, floor: response.data.room.floor, room_type_id: response.data.room.room_type_id, - status: response.data.room.status, + status: response.data.room.status as 'available' | 'occupied' | 'maintenance', featured: response.data.room.featured, price: response.data.room.price?.toString() || '', description: response.data.room.description || '', @@ -821,7 +822,7 @@ const ReceptionDashboardPage: React.FC = () => { room_number: room.room_number, floor: room.floor, room_type_id: room.room_type_id, - status: room.status, + status: room.status as 'available' | 'occupied' | 'maintenance', featured: room.featured, price: room.price?.toString() || '', description: room.description || '', @@ -856,7 +857,7 @@ const ReceptionDashboardPage: React.FC = () => { room_number: roomData.room_number, floor: roomData.floor, room_type_id: roomData.room_type_id, - status: roomData.status, + status: roomData.status as 'available' | 'occupied' | 'maintenance', featured: roomData.featured, price: roomData.price?.toString() || '', description: roomData.description || '', @@ -2683,13 +2684,13 @@ const ReceptionDashboardPage: React.FC = () => {

    - {payment.payment_status === 'completed' || payment.payment_status === 'paid' ? 'Paid' : payment.payment_status || 'Pending'} + {payment.payment_status === 'completed' ? 'Paid' : payment.payment_status || 'Pending'}
  • {payment.transaction_id && ( @@ -3078,7 +3079,7 @@ const ReceptionDashboardPage: React.FC = () => {