import React, { useState, useEffect } from 'react'; import { Award, Users, Search, Filter, Gift, Edit, Trash2, Plus, Power, PowerOff, X, Save } from 'lucide-react'; import { toast } from 'react-toastify'; import Loading from '../../shared/components/Loading'; import EmptyState from '../../shared/components/EmptyState'; import ConfirmationDialog from '../../shared/components/ConfirmationDialog'; import loyaltyService, { LoyaltyTier, LoyaltyReward } from '../../features/loyalty/services/loyaltyService'; import Pagination from '../../shared/components/Pagination'; type Tab = 'users' | 'tiers' | 'rewards'; interface UserLoyaltyData { user_id: number; user_name: string; user_email: string; tier: LoyaltyTier; total_points: number; lifetime_points: number; available_points: number; referral_count: number; tier_started_date?: string; } const LoyaltyManagementPage: React.FC = () => { const [activeTab, setActiveTab] = useState('users'); const [programEnabled, setProgramEnabled] = useState(true); const [loadingStatus, setLoadingStatus] = useState(false); const [users, setUsers] = useState([]); const [tiers, setTiers] = useState([]); const [rewards, setRewards] = useState([]); const [loading, setLoading] = useState(true); const [search, setSearch] = useState(''); const [selectedTier, setSelectedTier] = useState(null); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [totalItems, setTotalItems] = useState(0); const itemsPerPage = 20; // Modals state const [showTierModal, setShowTierModal] = useState(false); const [showRewardModal, setShowRewardModal] = useState(false); const [editingTier, setEditingTier] = useState(null); const [editingReward, setEditingReward] = useState(null); const [showDeleteConfirm, setShowDeleteConfirm] = useState<{type: 'tier' | 'reward', id: number, name: string} | null>(null); // Tier form state const [tierForm, setTierForm] = useState({ level: 'bronze' as 'bronze' | 'silver' | 'gold' | 'platinum', name: '', description: '', min_points: 0, points_earn_rate: 1.0, discount_percentage: 0, benefits: '', icon: '', color: '', is_active: true }); // Reward form state const [rewardForm, setRewardForm] = useState({ name: '', description: '', reward_type: 'discount' as 'discount' | 'room_upgrade' | 'amenity' | 'cashback' | 'voucher', points_cost: 0, discount_percentage: null as number | null, discount_amount: null as number | null, max_discount_amount: null as number | null, applicable_tier_id: null as number | null, min_booking_amount: null as number | null, icon: '', image: '', is_active: true, stock_quantity: null as number | null, valid_from: '', valid_until: '' }); useEffect(() => { fetchProgramStatus(); fetchTiers(); fetchRewards(); fetchUsers(); }, []); useEffect(() => { if (activeTab === 'users') { setCurrentPage(1); fetchUsers(); } }, [search, selectedTier, activeTab]); useEffect(() => { if (activeTab === 'users') { fetchUsers(); } }, [currentPage]); const fetchProgramStatus = async () => { try { const response = await loyaltyService.getProgramStatus(); setProgramEnabled(response.data.enabled); } catch (error: any) { console.error('Failed to load program status:', error); } }; const fetchTiers = async () => { try { const response = await loyaltyService.getAllTiers(); setTiers(response.data.tiers); } catch (error: any) { toast.error(error.message || 'Failed to load loyalty tiers'); } }; const fetchRewards = async () => { try { const response = await loyaltyService.getAllRewardsAdmin(); setRewards(response.data.rewards); } catch (error: any) { toast.error(error.message || 'Failed to load rewards'); } }; const fetchUsers = async () => { try { setLoading(true); const response = await loyaltyService.getUsersLoyaltyStatus( search || undefined, selectedTier || undefined, currentPage, itemsPerPage ); setUsers(response.data.users); setTotalPages(response.data.pagination.totalPages); setTotalItems(response.data.pagination.total); } catch (error: any) { toast.error(error.message || 'Failed to load user loyalty data'); } finally { setLoading(false); } }; const handleToggleProgram = async () => { try { setLoadingStatus(true); const newStatus = !programEnabled; await loyaltyService.updateProgramStatus(newStatus); setProgramEnabled(newStatus); toast.success(`Loyalty program ${newStatus ? 'enabled' : 'disabled'} successfully`); } catch (error: any) { toast.error(error.message || 'Failed to update program status'); } finally { setLoadingStatus(false); } }; const handleTierSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { if (editingTier) { await loyaltyService.updateTier(editingTier.id, tierForm); toast.success('Tier updated successfully'); } else { await loyaltyService.createTier(tierForm); toast.success('Tier created successfully'); } setShowTierModal(false); resetTierForm(); fetchTiers(); } catch (error: any) { toast.error(error.message || 'Failed to save tier'); } }; const handleRewardSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { const rewardData = { ...rewardForm, discount_percentage: rewardForm.discount_percentage || undefined, discount_amount: rewardForm.discount_amount || undefined, max_discount_amount: rewardForm.max_discount_amount || undefined, applicable_tier_id: rewardForm.applicable_tier_id || undefined, min_booking_amount: rewardForm.min_booking_amount || undefined, stock_quantity: rewardForm.stock_quantity || undefined, valid_from: rewardForm.valid_from || undefined, valid_until: rewardForm.valid_until || undefined }; if (editingReward) { await loyaltyService.updateReward(editingReward.id, rewardData); toast.success('Reward updated successfully'); } else { await loyaltyService.createReward(rewardData); toast.success('Reward created successfully'); } setShowRewardModal(false); resetRewardForm(); fetchRewards(); } catch (error: any) { toast.error(error.message || 'Failed to save reward'); } }; const handleEditTier = (tier: LoyaltyTier) => { setEditingTier(tier); setTierForm({ level: tier.level as 'bronze' | 'silver' | 'gold' | 'platinum', name: tier.name, description: tier.description || '', min_points: tier.min_points, points_earn_rate: tier.points_earn_rate, discount_percentage: tier.discount_percentage || 0, benefits: tier.benefits || '', icon: tier.icon || '', color: tier.color || '', is_active: tier.is_active ?? true }); setShowTierModal(true); }; const handleEditReward = (reward: LoyaltyReward) => { setEditingReward(reward); setRewardForm({ name: reward.name, description: reward.description || '', reward_type: reward.reward_type as any, points_cost: reward.points_cost, discount_percentage: reward.discount_percentage || null, discount_amount: reward.discount_amount || null, max_discount_amount: reward.max_discount_amount || null, applicable_tier_id: reward.applicable_tier_id || null, min_booking_amount: reward.min_booking_amount || null, icon: reward.icon || '', image: reward.image || '', is_active: reward.is_active ?? true, stock_quantity: reward.stock_quantity || null, valid_from: reward.valid_from ? reward.valid_from.split('T')[0] : '', valid_until: reward.valid_until ? reward.valid_until.split('T')[0] : '' }); setShowRewardModal(true); }; const handleDeleteTier = async (id: number) => { try { await loyaltyService.deleteTier(id); toast.success('Tier deleted successfully'); fetchTiers(); setShowDeleteConfirm(null); } catch (error: any) { toast.error(error.message || 'Failed to delete tier'); } }; const handleDeleteReward = async (id: number) => { try { await loyaltyService.deleteReward(id); toast.success('Reward deleted successfully'); fetchRewards(); setShowDeleteConfirm(null); } catch (error: any) { toast.error(error.message || 'Failed to delete reward'); } }; const resetTierForm = () => { setEditingTier(null); setTierForm({ level: 'bronze', name: '', description: '', min_points: 0, points_earn_rate: 1.0, discount_percentage: 0, benefits: '', icon: '', color: '', is_active: true }); }; const resetRewardForm = () => { setEditingReward(null); setRewardForm({ name: '', description: '', reward_type: 'discount', points_cost: 0, discount_percentage: null, discount_amount: null, max_discount_amount: null, applicable_tier_id: null, min_booking_amount: null, icon: '', image: '', is_active: true, stock_quantity: null, valid_from: '', valid_until: '' }); }; const getTierColor = (level?: string) => { switch (level) { case 'bronze': return 'bg-orange-100 text-orange-800 border-orange-200'; case 'silver': return 'bg-gray-100 text-gray-800 border-gray-200'; case 'gold': return 'bg-yellow-100 text-yellow-800 border-yellow-200'; case 'platinum': return 'bg-purple-100 text-purple-800 border-purple-200'; default: return 'bg-gray-100 text-gray-800 border-gray-200'; } }; return (
{/* Header */}

Loyalty Program Management

Manage loyalty program settings, tiers, and rewards

{/* Program Status Banner */}
{programEnabled ? ( <> Loyalty Program is ENABLED - Customers can earn and redeem points ) : ( <> Loyalty Program is DISABLED - No points will be awarded or redeemed )}
{/* Tabs */}
{/* Users Tab */} {activeTab === 'users' && ( <> {/* Filters */}
setSearch(e.target.value)} className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent" />
{/* Stats Cards */}
Total Members
{totalItems.toLocaleString()}
Total Points Distributed
{users.reduce((sum, user) => sum + user.lifetime_points, 0).toLocaleString()}
Active Referrals
{users.reduce((sum, user) => sum + user.referral_count, 0).toLocaleString()}
Available Points
{users.reduce((sum, user) => sum + user.available_points, 0).toLocaleString()}
{/* Users Table */}
{loading ? ( ) : users.length === 0 ? ( ) : ( <>
{users.map((user) => ( ))}
Customer Tier Lifetime Points Available Points Referrals Member Since
{user.user_name}
{user.user_email}
{user.tier?.name || 'N/A'}
{user.lifetime_points.toLocaleString()}
{user.available_points.toLocaleString()}
{user.referral_count}
{user.tier_started_date ? new Date(user.tier_started_date).toLocaleDateString() : 'N/A'}
{totalPages > 1 && (
)} )}
)} {/* Tiers Tab */} {activeTab === 'tiers' && ( <>

Loyalty Tiers

{tiers.length === 0 ? ( ) : (
{tiers.map((tier) => (

{tier.name}

{!tier.is_active && ( Inactive )}

{tier.description}

Min Points: {tier.min_points.toLocaleString()}
Earn Rate: {tier.points_earn_rate}x
{tier.discount_percentage > 0 && (
Discount: {tier.discount_percentage}%
)}
))}
)} )} {/* Rewards Tab */} {activeTab === 'rewards' && ( <>

Rewards Catalog

{rewards.length === 0 ? ( ) : (
{rewards.map((reward) => (
{reward.image && ( {reward.name} )}

{reward.name}

{!reward.is_available ? ( Unavailable ) : ( Active )}

{reward.description}

{reward.points_cost} points {reward.stock_quantity != null && reward.redeemed_count != null && ( {reward.stock_quantity - reward.redeemed_count} left )}
))}
)} )}
{/* Tier Modal */} {showTierModal && (

{editingTier ? 'Edit Tier' : 'Create New Tier'}

setTierForm({ ...tierForm, name: e.target.value })} className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500" required />