updates
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
<!-- Content Security Policy - Additional layer of XSS protection -->
|
||||
<!-- Allows HTTP localhost connections for development, HTTPS for production -->
|
||||
<!-- Note: Backend CSP headers (production only) will override/merge with this meta tag -->
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https: http: blob:; connect-src 'self' https: http://localhost:* http://127.0.0.1:* ws://localhost:* ws://127.0.0.1:* wss:; frame-src 'none'; object-src 'none'; base-uri 'self'; form-action 'self';" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://js.stripe.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https: http: blob:; connect-src 'self' https: http://localhost:* http://127.0.0.1:* ws://localhost:* ws://127.0.0.1:* wss: https://js.stripe.com https://hooks.stripe.com; frame-src 'self' https://js.stripe.com https://hooks.stripe.com; object-src 'none'; base-uri 'self'; form-action 'self';" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;600;700;800;900&family=Cormorant+Garamond:wght@300;400;500;600;700&family=Cinzel:wght@400;500;600;700&family=Poppins:wght@300;400;500;600;700&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet" />
|
||||
|
||||
@@ -86,6 +86,7 @@ const RatePlanManagementPage = lazy(() => import('./pages/admin/RatePlanManageme
|
||||
const PackageManagementPage = lazy(() => import('./pages/admin/PackageManagementPage'));
|
||||
const SecurityManagementPage = lazy(() => import('./pages/admin/SecurityManagementPage'));
|
||||
const EmailCampaignManagementPage = lazy(() => import('./pages/admin/EmailCampaignManagementPage'));
|
||||
const ReviewManagementPage = lazy(() => import('./pages/admin/ReviewManagementPage'));
|
||||
|
||||
const StaffDashboardPage = lazy(() => import('./pages/staff/DashboardPage'));
|
||||
const StaffBookingManagementPage = lazy(() => import('./pages/staff/BookingManagementPage'));
|
||||
@@ -464,6 +465,10 @@ function App() {
|
||||
path="email-campaigns"
|
||||
element={<EmailCampaignManagementPage />}
|
||||
/>
|
||||
<Route
|
||||
path="reviews"
|
||||
element={<ReviewManagementPage />}
|
||||
/>
|
||||
</Route>
|
||||
|
||||
{}
|
||||
|
||||
@@ -26,7 +26,8 @@ import {
|
||||
Mail,
|
||||
TrendingUp,
|
||||
Building2,
|
||||
Crown
|
||||
Crown,
|
||||
Star
|
||||
} from 'lucide-react';
|
||||
import useAuthStore from '../../store/useAuthStore';
|
||||
import { useResponsive } from '../../hooks';
|
||||
@@ -212,6 +213,11 @@ const SidebarAdmin: React.FC<SidebarAdminProps> = ({
|
||||
icon: Globe,
|
||||
label: 'Page Content'
|
||||
},
|
||||
{
|
||||
path: '/admin/reviews',
|
||||
icon: Star,
|
||||
label: 'Reviews'
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -74,9 +74,22 @@ const ReviewSection: React.FC<ReviewSectionProps> = ({
|
||||
setLoading(true);
|
||||
const response = await getRoomReviews(roomId);
|
||||
if (response.status === 'success' && response.data) {
|
||||
setReviews(response.data.reviews || []);
|
||||
setAverageRating(response.data.average_rating || 0);
|
||||
setTotalReviews(response.data.total_reviews || 0);
|
||||
const reviewsList = response.data.reviews || [];
|
||||
setReviews(reviewsList);
|
||||
// Use backend values, but fallback to calculated values if backend doesn't provide them
|
||||
const backendTotal = response.data.total_reviews || 0;
|
||||
const backendAverage = response.data.average_rating || 0;
|
||||
|
||||
// If backend doesn't provide totals but we have reviews, calculate them
|
||||
if (backendTotal === 0 && reviewsList.length > 0) {
|
||||
const calculatedTotal = reviewsList.length;
|
||||
const calculatedAverage = reviewsList.reduce((sum, r) => sum + r.rating, 0) / calculatedTotal;
|
||||
setTotalReviews(calculatedTotal);
|
||||
setAverageRating(calculatedAverage);
|
||||
} else {
|
||||
setTotalReviews(backendTotal);
|
||||
setAverageRating(backendAverage);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching reviews:', error);
|
||||
@@ -153,9 +166,11 @@ const ReviewSection: React.FC<ReviewSectionProps> = ({
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="text-center">
|
||||
<div className="text-2xl sm:text-3xl font-serif font-bold bg-gradient-to-r from-[#d4af37] to-[#f5d76e] bg-clip-text text-transparent">
|
||||
{averageRating > 0
|
||||
{totalReviews > 0 && averageRating > 0
|
||||
? averageRating.toFixed(1)
|
||||
: 'N/A'}
|
||||
: totalReviews === 0
|
||||
? '—'
|
||||
: averageRating.toFixed(1)}
|
||||
</div>
|
||||
<div className="mt-1">
|
||||
<RatingStars
|
||||
@@ -164,7 +179,9 @@ const ReviewSection: React.FC<ReviewSectionProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<div className="text-[10px] sm:text-xs text-gray-400 mt-1.5 font-light">
|
||||
{totalReviews} review{totalReviews !== 1 ? 's' : ''}
|
||||
{totalReviews > 0
|
||||
? `${totalReviews} review${totalReviews !== 1 ? 's' : ''}`
|
||||
: 'No reviews yet'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -274,7 +291,7 @@ const ReviewSection: React.FC<ReviewSectionProps> = ({
|
||||
{}
|
||||
<div>
|
||||
<h4 className="text-xs sm:text-sm font-serif font-semibold text-white mb-3 tracking-wide">
|
||||
All Reviews ({totalReviews})
|
||||
All Reviews ({reviews.length > 0 ? reviews.length : totalReviews})
|
||||
</h4>
|
||||
|
||||
{loading ? (
|
||||
|
||||
@@ -146,7 +146,12 @@ const ReviewManagementPage: React.FC = () => {
|
||||
{reviews.map((review) => (
|
||||
<tr key={review.id} className="hover:bg-gray-50">
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="text-sm font-medium text-gray-900">{review.user?.name}</div>
|
||||
<div className="text-sm font-medium text-gray-900">
|
||||
{review.user?.full_name || review.user?.name || 'Unknown User'}
|
||||
</div>
|
||||
{review.user?.email && (
|
||||
<div className="text-xs text-gray-500">{review.user.email}</div>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="text-sm text-gray-900">
|
||||
|
||||
Reference in New Issue
Block a user