Backup: 2025-07-21 18:13 - I got Claude Code
Some checks failed
CI/CD Pipeline / Backend Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Tests (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
E2E Tests / E2E Tests - ${{ github.event.inputs.environment || 'staging' }} (push) Has been cancelled
E2E Tests / Notify Results (push) Has been cancelled
Dependency Updates / Update Dependencies (push) Has been cancelled
Some checks failed
CI/CD Pipeline / Backend Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Tests (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
E2E Tests / E2E Tests - ${{ github.event.inputs.environment || 'staging' }} (push) Has been cancelled
E2E Tests / Notify Results (push) Has been cancelled
Dependency Updates / Update Dependencies (push) Has been cancelled
[Restore from backup: vip-coordinator-backup-2025-07-21-18-13-I got Claude Code]
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { apiCall } from '../utils/api';
|
||||
import { apiCall } from '../config/api';
|
||||
|
||||
interface ScheduleEvent {
|
||||
id: string;
|
||||
@@ -83,27 +83,28 @@ const Dashboard: React.FC = () => {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
const [vipsResult, driversResult] = await Promise.all([
|
||||
const [vipsResponse, driversResponse] = await Promise.all([
|
||||
apiCall('/api/vips', { headers: authHeaders }),
|
||||
apiCall('/api/drivers', { headers: authHeaders })
|
||||
]);
|
||||
|
||||
const vipsData = vipsResult.data;
|
||||
const driversData = driversResult.data;
|
||||
|
||||
if (!vipsData || !driversData) {
|
||||
if (!vipsResponse.ok || !driversResponse.ok) {
|
||||
throw new Error('Failed to fetch data');
|
||||
}
|
||||
|
||||
const vipsData = await vipsResponse.json();
|
||||
const driversData = await driversResponse.json();
|
||||
|
||||
// Fetch schedule for each VIP and determine current/next events
|
||||
const vipsWithSchedules = await Promise.all(
|
||||
vipsData.map(async (vip: Vip) => {
|
||||
try {
|
||||
const { data: scheduleData } = await apiCall(`/api/vips/${vip.id}/schedule`, {
|
||||
const scheduleResponse = await apiCall(`/api/vips/${vip.id}/schedule`, {
|
||||
headers: authHeaders
|
||||
});
|
||||
|
||||
if (scheduleData) {
|
||||
if (scheduleResponse.ok) {
|
||||
const scheduleData = await scheduleResponse.json();
|
||||
|
||||
const currentEvent = getCurrentEvent(scheduleData);
|
||||
const nextEvent = getNextEvent(scheduleData);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { apiCall } from '../utils/api';
|
||||
import { apiCall } from '../config/api';
|
||||
import GanttChart from '../components/GanttChart';
|
||||
|
||||
interface DriverScheduleEvent {
|
||||
@@ -42,14 +42,15 @@ const DriverDashboard: React.FC = () => {
|
||||
const fetchDriverSchedule = async () => {
|
||||
try {
|
||||
const token = localStorage.getItem('authToken');
|
||||
const { data } = await apiCall(`/api/drivers/${driverId}/schedule`, {
|
||||
const response = await apiCall(`/api/drivers/${driverId}/schedule`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (data) {
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setScheduleData(data);
|
||||
} else {
|
||||
setError('Driver not found');
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { apiCall } from '../utils/api';
|
||||
import { apiCall } from '../config/api';
|
||||
import DriverForm from '../components/DriverForm';
|
||||
import EditDriverForm from '../components/EditDriverForm';
|
||||
import { Driver, DriverFormData } from '../types';
|
||||
import { useToast } from '../contexts/ToastContext';
|
||||
import { LoadingSpinner } from '../components/LoadingSpinner';
|
||||
|
||||
interface Driver {
|
||||
id: string;
|
||||
name: string;
|
||||
phone: string;
|
||||
currentLocation: { lat: number; lng: number };
|
||||
assignedVipIds: string[];
|
||||
vehicleCapacity?: number;
|
||||
}
|
||||
|
||||
const DriverList: React.FC = () => {
|
||||
const { showToast } = useToast();
|
||||
const [drivers, setDrivers] = useState<Driver[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [editingDriver, setEditingDriver] = useState<Driver | null>(null);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
|
||||
// Function to extract last name for sorting
|
||||
const getLastName = (fullName: string) => {
|
||||
@@ -34,18 +38,19 @@ const DriverList: React.FC = () => {
|
||||
const fetchDrivers = async () => {
|
||||
try {
|
||||
const token = localStorage.getItem('authToken');
|
||||
const { data } = await apiCall('/api/drivers', {
|
||||
const response = await apiCall('/api/drivers', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (data) {
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
const sortedDrivers = sortDriversByLastName(data);
|
||||
setDrivers(sortedDrivers);
|
||||
} else {
|
||||
console.error('Failed to fetch drivers');
|
||||
console.error('Failed to fetch drivers:', response.status);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching drivers:', error);
|
||||
@@ -57,7 +62,7 @@ const DriverList: React.FC = () => {
|
||||
fetchDrivers();
|
||||
}, []);
|
||||
|
||||
const handleAddDriver = async (driverData: DriverFormData) => {
|
||||
const handleAddDriver = async (driverData: any) => {
|
||||
try {
|
||||
const token = localStorage.getItem('authToken');
|
||||
const response = await apiCall('/api/drivers', {
|
||||
@@ -73,18 +78,15 @@ const DriverList: React.FC = () => {
|
||||
const newDriver = await response.json();
|
||||
setDrivers(prev => sortDriversByLastName([...prev, newDriver]));
|
||||
setShowForm(false);
|
||||
showToast('Driver added successfully!', 'success');
|
||||
} else {
|
||||
console.error('Failed to add driver:', response.status);
|
||||
showToast('Failed to add driver. Please try again.', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error adding driver:', error);
|
||||
showToast('An error occurred while adding the driver.', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditDriver = async (driverData: DriverFormData) => {
|
||||
const handleEditDriver = async (driverData: any) => {
|
||||
try {
|
||||
const token = localStorage.getItem('authToken');
|
||||
const response = await apiCall(`/api/drivers/${driverData.id}`, {
|
||||
@@ -102,14 +104,11 @@ const DriverList: React.FC = () => {
|
||||
driver.id === updatedDriver.id ? updatedDriver : driver
|
||||
)));
|
||||
setEditingDriver(null);
|
||||
showToast('Driver updated successfully!', 'success');
|
||||
} else {
|
||||
console.error('Failed to update driver:', response.status);
|
||||
showToast('Failed to update driver. Please try again.', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating driver:', error);
|
||||
showToast('An error occurred while updating the driver.', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -130,39 +129,30 @@ const DriverList: React.FC = () => {
|
||||
|
||||
if (response.ok) {
|
||||
setDrivers(prev => prev.filter(driver => driver.id !== driverId));
|
||||
showToast('Driver deleted successfully!', 'success');
|
||||
} else {
|
||||
console.error('Failed to delete driver:', response.status);
|
||||
showToast('Failed to delete driver. Please try again.', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting driver:', error);
|
||||
showToast('An error occurred while deleting the driver.', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-screen bg-gradient-to-br from-slate-50 to-slate-100">
|
||||
<LoadingSpinner size="lg" message="Loading drivers..." />
|
||||
<div className="flex justify-center items-center min-h-64">
|
||||
<div className="bg-white rounded-2xl shadow-lg p-8 flex items-center space-x-4">
|
||||
<div className="w-8 h-8 border-4 border-blue-600 border-t-transparent rounded-full animate-spin"></div>
|
||||
<span className="text-lg font-medium text-slate-700">Loading drivers...</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Filter drivers based on search term
|
||||
const filteredDrivers = drivers.filter(driver => {
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
return (
|
||||
driver.name.toLowerCase().includes(searchLower) ||
|
||||
driver.phone.toLowerCase().includes(searchLower)
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
{/* Header */}
|
||||
<div className="bg-white rounded-2xl shadow-lg p-8 border border-slate-200/60">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold bg-gradient-to-r from-slate-800 to-slate-600 bg-clip-text text-transparent">
|
||||
Driver Management
|
||||
@@ -181,53 +171,16 @@ const DriverList: React.FC = () => {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Search Bar */}
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search by name or phone number..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full px-4 py-3 pl-12 border border-slate-200 rounded-lg focus:ring-2 focus:ring-amber-500 focus:border-transparent transition-all"
|
||||
/>
|
||||
<svg className="absolute left-4 top-3.5 h-5 w-5 text-slate-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
{searchTerm && (
|
||||
<button
|
||||
onClick={() => setSearchTerm('')}
|
||||
className="absolute right-4 top-3.5 text-slate-400 hover:text-slate-600"
|
||||
>
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Search Results */}
|
||||
{searchTerm && (
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-lg px-4 py-2 mb-4">
|
||||
<p className="text-sm text-amber-800">
|
||||
Found {filteredDrivers.length} result{filteredDrivers.length !== 1 ? 's' : ''} for "{searchTerm}"
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Driver Grid */}
|
||||
{filteredDrivers.length === 0 ? (
|
||||
{drivers.length === 0 ? (
|
||||
<div className="bg-white rounded-2xl shadow-lg p-12 border border-slate-200/60 text-center">
|
||||
<div className="w-16 h-16 bg-slate-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<div className="w-8 h-8 bg-slate-300 rounded-full"></div>
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-slate-800 mb-2">
|
||||
{searchTerm ? 'No Drivers Found' : 'No Drivers Added Yet'}
|
||||
</h3>
|
||||
<p className="text-slate-600 mb-6">
|
||||
{searchTerm ? `No drivers match your search for "${searchTerm}"` : 'Get started by adding your first driver'}
|
||||
</p>
|
||||
<h3 className="text-lg font-semibold text-slate-800 mb-2">No Drivers Found</h3>
|
||||
<p className="text-slate-600 mb-6">Get started by adding your first driver</p>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={() => setShowForm(true)}
|
||||
@@ -237,7 +190,7 @@ const DriverList: React.FC = () => {
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{filteredDrivers.map((driver) => (
|
||||
{drivers.map((driver) => (
|
||||
<div key={driver.id} className="bg-white rounded-2xl shadow-lg border border-slate-200/60 overflow-hidden hover:shadow-xl transition-shadow duration-200">
|
||||
<div className="p-6">
|
||||
{/* Driver Header */}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { apiCall } from '../utils/api';
|
||||
import { apiCall } from '../config/api';
|
||||
import FlightStatus from '../components/FlightStatus';
|
||||
import ScheduleManager from '../components/ScheduleManager';
|
||||
|
||||
@@ -37,14 +37,15 @@ const VipDetails: React.FC = () => {
|
||||
const fetchVip = async () => {
|
||||
try {
|
||||
const token = localStorage.getItem('authToken');
|
||||
const { data: vips } = await apiCall('/api/vips', {
|
||||
const response = await apiCall('/api/vips', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (vips) {
|
||||
if (response.ok) {
|
||||
const vips = await response.json();
|
||||
const foundVip = vips.find((v: Vip) => v.id === id);
|
||||
|
||||
if (foundVip) {
|
||||
@@ -73,14 +74,15 @@ const VipDetails: React.FC = () => {
|
||||
if (vip) {
|
||||
try {
|
||||
const token = localStorage.getItem('authToken');
|
||||
const { data: scheduleData } = await apiCall(`/api/vips/${vip.id}/schedule`, {
|
||||
const response = await apiCall(`/api/vips/${vip.id}/schedule`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (scheduleData) {
|
||||
if (response.ok) {
|
||||
const scheduleData = await response.json();
|
||||
setSchedule(scheduleData);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { apiCall } from '../utils/api';
|
||||
import { apiCall } from '../config/api';
|
||||
import VipForm from '../components/VipForm';
|
||||
import EditVipForm from '../components/EditVipForm';
|
||||
import FlightStatus from '../components/FlightStatus';
|
||||
import { useToast } from '../contexts/ToastContext';
|
||||
import { LoadingSpinner } from '../components/LoadingSpinner';
|
||||
|
||||
interface Vip {
|
||||
id: string;
|
||||
@@ -28,13 +26,10 @@ interface Vip {
|
||||
}
|
||||
|
||||
const VipList: React.FC = () => {
|
||||
const { showToast } = useToast();
|
||||
const [vips, setVips] = useState<Vip[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [editingVip, setEditingVip] = useState<Vip | null>(null);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
|
||||
// Function to extract last name for sorting
|
||||
const getLastName = (fullName: string) => {
|
||||
@@ -55,18 +50,19 @@ const VipList: React.FC = () => {
|
||||
const fetchVips = async () => {
|
||||
try {
|
||||
const token = localStorage.getItem('authToken');
|
||||
const { data } = await apiCall('/api/vips', {
|
||||
const response = await apiCall('/api/vips', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (data) {
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
const sortedVips = sortVipsByLastName(data);
|
||||
setVips(sortedVips);
|
||||
} else {
|
||||
console.error('Failed to fetch VIPs');
|
||||
console.error('Failed to fetch VIPs:', response.status);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching VIPs:', error);
|
||||
@@ -79,7 +75,6 @@ const VipList: React.FC = () => {
|
||||
}, []);
|
||||
|
||||
const handleAddVip = async (vipData: any) => {
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const token = localStorage.getItem('authToken');
|
||||
const response = await apiCall('/api/vips', {
|
||||
@@ -95,15 +90,11 @@ const VipList: React.FC = () => {
|
||||
const newVip = await response.json();
|
||||
setVips(prev => sortVipsByLastName([...prev, newVip]));
|
||||
setShowForm(false);
|
||||
showToast('VIP added successfully!', 'success');
|
||||
} else {
|
||||
showToast('Failed to add VIP. Please try again.', 'error');
|
||||
console.error('Failed to add VIP:', response.status);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error adding VIP:', error);
|
||||
showToast('An error occurred while adding the VIP.', 'error');
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -123,13 +114,11 @@ const VipList: React.FC = () => {
|
||||
const updatedVip = await response.json();
|
||||
setVips(prev => sortVipsByLastName(prev.map(vip => vip.id === updatedVip.id ? updatedVip : vip)));
|
||||
setEditingVip(null);
|
||||
showToast('VIP updated successfully!', 'success');
|
||||
} else {
|
||||
showToast('Failed to update VIP. Please try again.', 'error');
|
||||
console.error('Failed to update VIP:', response.status);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating VIP:', error);
|
||||
showToast('An error occurred while updating the VIP.', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -150,42 +139,30 @@ const VipList: React.FC = () => {
|
||||
|
||||
if (response.ok) {
|
||||
setVips(prev => prev.filter(vip => vip.id !== vipId));
|
||||
showToast('VIP deleted successfully!', 'success');
|
||||
} else {
|
||||
showToast('Failed to delete VIP. Please try again.', 'error');
|
||||
console.error('Failed to delete VIP:', response.status);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting VIP:', error);
|
||||
showToast('An error occurred while deleting the VIP.', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-screen bg-gradient-to-br from-slate-50 to-slate-100">
|
||||
<LoadingSpinner size="lg" message="Loading VIPs..." />
|
||||
<div className="flex justify-center items-center min-h-64">
|
||||
<div className="bg-white rounded-2xl shadow-lg p-8 flex items-center space-x-4">
|
||||
<div className="w-8 h-8 border-4 border-blue-600 border-t-transparent rounded-full animate-spin"></div>
|
||||
<span className="text-lg font-medium text-slate-700">Loading VIPs...</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Filter VIPs based on search term
|
||||
const filteredVips = vips.filter(vip => {
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
return (
|
||||
vip.name.toLowerCase().includes(searchLower) ||
|
||||
vip.organization.toLowerCase().includes(searchLower) ||
|
||||
vip.department.toLowerCase().includes(searchLower) ||
|
||||
(vip.transportMode === 'flight' && vip.flights?.some(flight =>
|
||||
flight.flightNumber.toLowerCase().includes(searchLower)
|
||||
))
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
{/* Header */}
|
||||
<div className="bg-white rounded-2xl shadow-lg p-8 border border-slate-200/60">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold bg-gradient-to-r from-slate-800 to-slate-600 bg-clip-text text-transparent">
|
||||
VIP Management
|
||||
@@ -199,52 +176,16 @@ const VipList: React.FC = () => {
|
||||
Add New VIP
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Search Bar */}
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search by name, organization, department, or flight number..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full px-4 py-3 pl-12 border border-slate-200 rounded-lg focus:ring-2 focus:ring-amber-500 focus:border-transparent transition-all"
|
||||
/>
|
||||
<svg className="absolute left-4 top-3.5 h-5 w-5 text-slate-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
{searchTerm && (
|
||||
<button
|
||||
onClick={() => setSearchTerm('')}
|
||||
className="absolute right-4 top-3.5 text-slate-400 hover:text-slate-600"
|
||||
>
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* VIP List */}
|
||||
{searchTerm && (
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-lg px-4 py-2 mb-4">
|
||||
<p className="text-sm text-amber-800">
|
||||
Found {filteredVips.length} result{filteredVips.length !== 1 ? 's' : ''} for "{searchTerm}"
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{filteredVips.length === 0 ? (
|
||||
{vips.length === 0 ? (
|
||||
<div className="bg-white rounded-2xl shadow-lg p-12 border border-slate-200/60 text-center">
|
||||
<div className="w-16 h-16 bg-slate-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<div className="w-8 h-8 bg-slate-300 rounded-full"></div>
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-slate-800 mb-2">
|
||||
{searchTerm ? 'No VIPs Found' : 'No VIPs Added Yet'}
|
||||
</h3>
|
||||
<p className="text-slate-600 mb-6">
|
||||
{searchTerm ? `No VIPs match your search for "${searchTerm}"` : 'Get started by adding your first VIP'}
|
||||
</p>
|
||||
<h3 className="text-lg font-semibold text-slate-800 mb-2">No VIPs Found</h3>
|
||||
<p className="text-slate-600 mb-6">Get started by adding your first VIP</p>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={() => setShowForm(true)}
|
||||
@@ -254,7 +195,7 @@ const VipList: React.FC = () => {
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{filteredVips.map((vip) => (
|
||||
{vips.map((vip) => (
|
||||
<div key={vip.id} className="bg-white rounded-2xl shadow-lg border border-slate-200/60 overflow-hidden hover:shadow-xl transition-shadow duration-200">
|
||||
<div className="p-6">
|
||||
<div className="flex justify-between items-start">
|
||||
|
||||
Reference in New Issue
Block a user