835 lines
24 KiB
TypeScript
835 lines
24 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import {
|
|
Server,
|
|
Database,
|
|
Users,
|
|
Activity,
|
|
RefreshCw,
|
|
Plus,
|
|
Edit3,
|
|
Trash2,
|
|
Eye,
|
|
AlertCircle,
|
|
CheckCircle,
|
|
XCircle
|
|
} from 'lucide-react';
|
|
|
|
import { useEffect } from 'react';
|
|
|
|
function App() {
|
|
useEffect(() => {
|
|
const ws = new WebSocket('ws://localhost:8000/ws/logs');
|
|
|
|
ws.onmessage = (event) => {
|
|
const data = JSON.parse(event.data);
|
|
if (data.type === 'log') {
|
|
console.log(`[Backend Log]: ${data.message}`);
|
|
}
|
|
};
|
|
|
|
ws.onclose = () => {
|
|
console.log('WebSocket connection closed');
|
|
};
|
|
|
|
return () => {
|
|
ws.close();
|
|
};
|
|
}, []);
|
|
|
|
return <div>Your React App</div>;
|
|
}
|
|
|
|
const Dashboard = () => {
|
|
const [apiUrl, setApiUrl] = useState('http://127.0.0.1:8000');
|
|
const [connectionStatus, setConnectionStatus] = useState('disconnected');
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [activeTab, setActiveTab] = useState('dashboard');
|
|
const [members, setMembers] = useState([]);
|
|
const [totalMembers, setTotalMembers] = useState(0);
|
|
const [activeMembersCount, setActiveMembersCount] = useState(0);
|
|
const [inactiveMembersCount, setInactiveMembersCount] = useState(0);
|
|
const [deactivatedMembersCount, setDeactivatedMembersCount] = useState(0);
|
|
const [loadingData, setLoadingData] = useState(false);
|
|
|
|
const testConnection = async () => {
|
|
setIsLoading(true);
|
|
try {
|
|
console.log('Testing connection to:', apiUrl);
|
|
// Test with a simple endpoint first - try /member/1 since /member/100 works
|
|
const response = await fetch(`${apiUrl}/member/100`);
|
|
|
|
if (response.ok) {
|
|
setConnectionStatus('connected');
|
|
console.log('Connection successful!');
|
|
// Load members data when connection is successful
|
|
await loadMembersCount();
|
|
} else {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
} catch (error) {
|
|
console.error('Connection error:', error);
|
|
setConnectionStatus('error');
|
|
}
|
|
setIsLoading(false);
|
|
};
|
|
|
|
const loadMembersCount = async () => {
|
|
if (connectionStatus !== 'connected' && !isLoading) return;
|
|
|
|
setLoadingData(true);
|
|
console.log('Attempting to load active members count...');
|
|
|
|
// const fetchActiveMembersCount = async () => {
|
|
try {
|
|
const response = await fetch(`${apiUrl}/activememberscount`);
|
|
if (response.ok) {
|
|
const count = await response.json(); // Single number return
|
|
setTotalMembers(count);
|
|
console.log('Total active members count:', count);
|
|
} else {
|
|
console.log('Failed to fetch active members count:', response.statusText);
|
|
setTotalMembers(0);
|
|
}
|
|
} catch (error) {
|
|
console.log('Error fetching active members count:', error);
|
|
setMembers([]);
|
|
setTotalMembers(0);
|
|
}
|
|
//}
|
|
|
|
setLoadingData(false);
|
|
};
|
|
|
|
// Fetch member counts
|
|
const fetchMemberCounts = async () => {
|
|
if (connectionStatus !== 'connected' && !isLoading) return;
|
|
|
|
try {
|
|
// Fetch active members count
|
|
const activeResponse = await fetch(`${apiUrl}/activememberscount`, {
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
if (activeResponse.ok) {
|
|
const activeCount = await activeResponse.json();
|
|
setActiveMembersCount(activeCount);
|
|
console.log('activememberscount: ', activeCount);
|
|
}
|
|
|
|
// Fetch inactive members count
|
|
const inactiveResponse = await fetch(`${apiUrl}/inactivememberscount`, {
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
if (inactiveResponse.ok) {
|
|
const inactiveCount = await inactiveResponse.json();
|
|
setInactiveMembersCount(inactiveCount);
|
|
}
|
|
|
|
// Fetch deactivated members count
|
|
const deactivatedResponse = await fetch(`${apiUrl}/deactivatedmemberscount`, {
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
if (deactivatedResponse.ok) {
|
|
const deactivatedCount = await deactivatedResponse.json();
|
|
setDeactivatedMembersCount(deactivatedCount);
|
|
}
|
|
} catch (err) {
|
|
console.error('Error fetching member counts:', err);
|
|
}
|
|
};
|
|
|
|
// Fetch data from API
|
|
const fetchData = async (endpoint = 'items') => {
|
|
if (connectionStatus !== 'connected' && !isLoading) return;
|
|
|
|
setLoadingData(true);
|
|
try {
|
|
const response = await fetch(`${apiUrl}/${endpoint}`, {
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
});
|
|
|
|
if (response.ok) {
|
|
const result = await response.json();
|
|
setData(Array.isArray(result) ? result : [result]);
|
|
} else {
|
|
throw new Error(`Failed to fetch data: ${response.statusText}`);
|
|
}
|
|
} catch (err) {
|
|
setError(err.message);
|
|
} finally {
|
|
setLoadingData(false);
|
|
}
|
|
};
|
|
|
|
const loadMembers = async () => {
|
|
if (connectionStatus !== 'connected' && !isLoading) return;
|
|
|
|
setLoadingData(true);
|
|
try {
|
|
// Since /members/ gives 500 error, let's try different approaches
|
|
console.log('Attempting to load active members count...');
|
|
|
|
// Option 1: Try individual member requests (if you know IDs)
|
|
// For now, let's try to get a few members by ID
|
|
const memberIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // Adjust these IDs as needed
|
|
const memberPromises = memberIds.map(async (id) => {
|
|
try {
|
|
const response = await fetch(`${apiUrl}/member/${id}`);
|
|
if (response.ok) {
|
|
return await response.json();
|
|
}
|
|
return null;
|
|
} catch (error) {
|
|
console.log(`Member ${id} not found or error:`, error);
|
|
return null;
|
|
}
|
|
});
|
|
|
|
const results = await Promise.all(memberPromises);
|
|
const validMembers = results.filter(member => member !== null);
|
|
|
|
console.log('Found members:', validMembers);
|
|
setMembers(validMembers);
|
|
setTotalMembers(validMembers.length);
|
|
|
|
if (validMembers.length === 0) {
|
|
console.warn('No members found with IDs 1-10. You may need to adjust the ID range or fix the /members/ endpoint.');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Error loading members:', error);
|
|
setMembers([]);
|
|
setTotalMembers(0);
|
|
}
|
|
setLoadingData(false);
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (connectionStatus === 'connected') {
|
|
// loadMembersCount();
|
|
fetchMemberCounts();
|
|
if (activeTab === 'data') {
|
|
fetchData();
|
|
}
|
|
}
|
|
}, [connectionStatus]);
|
|
|
|
const getStatusColor = () => {
|
|
switch (connectionStatus) {
|
|
case 'connected': return '#10b981';
|
|
case 'error': return '#ef4444';
|
|
default: return '#f59e0b';
|
|
}
|
|
};
|
|
|
|
const getStatusIcon = () => {
|
|
switch (connectionStatus) {
|
|
case 'connected': return <CheckCircle size={16} />;
|
|
case 'error': return <XCircle size={16} />;
|
|
default: return <AlertCircle size={16} />;
|
|
}
|
|
};
|
|
|
|
const getStatusText = () => {
|
|
switch (connectionStatus) {
|
|
case 'connected': return 'Connected';
|
|
case 'error': return 'Connection Error';
|
|
default: return 'Disconnected';
|
|
}
|
|
};
|
|
|
|
const getFullName = (member) => {
|
|
const firstName = member.member_firstname || '';
|
|
const lastName = member.member_surname || '';
|
|
const memberSuffix = member.member_suffix || '';
|
|
const fullName = `${firstName} ${lastName} ${memberSuffix}`.trim();
|
|
return fullName || 'N/A';
|
|
};
|
|
|
|
const MEMBER_STATE_COLORS = {
|
|
'A': '#10B981', // Green - Active
|
|
'I': '#F59E0B', // Amber/Orange - Inactive
|
|
'D': '#EF4444' // Red - Deactivated
|
|
};
|
|
|
|
const getMemberStateColor = (stateCode) => {
|
|
return MEMBER_STATE_COLORS[stateCode] || '#6B7280'; // Gray as fallback
|
|
};
|
|
|
|
const styles = {
|
|
container: {
|
|
minHeight: '100vh',
|
|
backgroundColor: '#f9fafb',
|
|
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
|
|
},
|
|
header: {
|
|
backgroundColor: 'white',
|
|
boxShadow: '0 1px 3px 0 rgba(0, 0, 0, 0.1)',
|
|
borderBottom: '1px solid #e5e7eb'
|
|
},
|
|
headerContent: {
|
|
maxWidth: '1280px',
|
|
margin: '0 auto',
|
|
padding: '0 24px',
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
paddingTop: '24px',
|
|
paddingBottom: '24px'
|
|
},
|
|
title: {
|
|
fontSize: '30px',
|
|
fontWeight: 'bold',
|
|
color: '#111827',
|
|
margin: 0
|
|
},
|
|
subtitle: {
|
|
color: '#6b7280',
|
|
marginTop: '4px',
|
|
fontSize: '16px'
|
|
},
|
|
statusContainer: {
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: '8px',
|
|
color: getStatusColor(),
|
|
fontWeight: '500'
|
|
},
|
|
mainContent: {
|
|
maxWidth: '1280px',
|
|
margin: '0 auto',
|
|
padding: '32px 24px'
|
|
},
|
|
card: {
|
|
backgroundColor: 'white',
|
|
borderRadius: '8px',
|
|
boxShadow: '0 1px 3px 0 rgba(0, 0, 0, 0.1)',
|
|
border: '1px solid #e5e7eb',
|
|
padding: '24px',
|
|
marginBottom: '32px'
|
|
},
|
|
cardTitle: {
|
|
fontSize: '18px',
|
|
fontWeight: '600',
|
|
color: '#111827',
|
|
marginBottom: '16px'
|
|
},
|
|
connectionForm: {
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: '16px'
|
|
},
|
|
input: {
|
|
flex: 1,
|
|
padding: '8px 12px',
|
|
border: '1px solid #d1d5db',
|
|
borderRadius: '6px',
|
|
fontSize: '14px',
|
|
outline: 'none'
|
|
},
|
|
button: {
|
|
display: 'inline-flex',
|
|
alignItems: 'center',
|
|
gap: '8px',
|
|
padding: '8px 16px',
|
|
backgroundColor: '#2563eb',
|
|
color: 'white',
|
|
border: 'none',
|
|
borderRadius: '6px',
|
|
fontSize: '14px',
|
|
fontWeight: '500',
|
|
cursor: 'pointer',
|
|
transition: 'background-color 0.2s'
|
|
},
|
|
buttonDisabled: {
|
|
opacity: 0.5,
|
|
cursor: 'not-allowed'
|
|
},
|
|
errorAlert: {
|
|
marginTop: '16px',
|
|
padding: '16px',
|
|
backgroundColor: '#fef2f2',
|
|
border: '1px solid #fecaca',
|
|
borderRadius: '6px',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: '8px'
|
|
},
|
|
errorText: {
|
|
color: '#991b1b'
|
|
},
|
|
tabs: {
|
|
borderBottom: '1px solid #e5e7eb',
|
|
marginBottom: '32px'
|
|
},
|
|
tabsNav: {
|
|
display: 'flex',
|
|
gap: '32px'
|
|
},
|
|
tab: {
|
|
padding: '8px 4px',
|
|
fontSize: '14px',
|
|
fontWeight: '500',
|
|
border: 'none',
|
|
background: 'none',
|
|
cursor: 'pointer',
|
|
borderBottom: '2px solid transparent',
|
|
transition: 'all 0.2s'
|
|
},
|
|
tabActive: {
|
|
borderBottom: '2px solid #2563eb',
|
|
color: '#2563eb'
|
|
},
|
|
tabInactive: {
|
|
color: '#6b7280'
|
|
},
|
|
statsGrid: {
|
|
display: 'grid',
|
|
gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))',
|
|
gap: '24px',
|
|
marginBottom: '32px'
|
|
},
|
|
statCard: {
|
|
backgroundColor: 'white',
|
|
borderRadius: '8px',
|
|
boxShadow: '0 1px 3px 0 rgba(0, 0, 0, 0.1)',
|
|
border: '1px solid #e5e7eb',
|
|
padding: '24px',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between'
|
|
},
|
|
statContent: {
|
|
flex: 1
|
|
},
|
|
statLabel: {
|
|
fontSize: '14px',
|
|
fontWeight: '500',
|
|
color: '#6b7280'
|
|
},
|
|
statValue: {
|
|
fontSize: '24px',
|
|
fontWeight: 'bold',
|
|
color: '#111827',
|
|
marginTop: '8px'
|
|
},
|
|
statIcon: {
|
|
padding: '12px',
|
|
borderRadius: '50%'
|
|
},
|
|
iconOrange: {
|
|
backgroundColor: '#fed7aa',
|
|
color: '#ea580c'
|
|
},
|
|
iconBlue: {
|
|
backgroundColor: '#dbeafe',
|
|
color: '#2563eb'
|
|
},
|
|
iconGreen: {
|
|
backgroundColor: '#dcfce7',
|
|
color: '#16a34a'
|
|
},
|
|
iconPurple: {
|
|
backgroundColor: '#e9d5ff',
|
|
color: '#9333ea'
|
|
},
|
|
actionButtons: {
|
|
display: 'flex',
|
|
flexWrap: 'wrap',
|
|
gap: '16px'
|
|
},
|
|
actionButton: {
|
|
display: 'inline-flex',
|
|
alignItems: 'center',
|
|
gap: '8px',
|
|
padding: '8px 16px',
|
|
border: 'none',
|
|
borderRadius: '6px',
|
|
fontSize: '14px',
|
|
fontWeight: '500',
|
|
cursor: 'pointer',
|
|
transition: 'background-color 0.2s'
|
|
},
|
|
actionButtonGray: {
|
|
backgroundColor: '#f3f4f6',
|
|
color: '#374151'
|
|
},
|
|
actionButtonGreen: {
|
|
backgroundColor: '#dcfce7',
|
|
color: '#166534'
|
|
},
|
|
actionButtonBlue: {
|
|
backgroundColor: '#dbeafe',
|
|
color: '#1e40af'
|
|
},
|
|
emptyState: {
|
|
textAlign: 'center',
|
|
padding: '32px'
|
|
},
|
|
emptyStateIcon: {
|
|
margin: '0 auto 16px',
|
|
color: '#9ca3af'
|
|
},
|
|
emptyStateText: {
|
|
color: '#6b7280'
|
|
},
|
|
cardHeader: {
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
marginBottom: '20px'
|
|
},
|
|
|
|
refreshButton: {
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
padding: '8px 16px',
|
|
backgroundColor: '#3B82F6',
|
|
color: 'white',
|
|
border: 'none',
|
|
borderRadius: '6px',
|
|
cursor: 'pointer',
|
|
fontSize: '14px',
|
|
fontWeight: '500'
|
|
},
|
|
|
|
loadingState: {
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
alignItems: 'center',
|
|
padding: '40px',
|
|
color: '#6B7280'
|
|
},
|
|
|
|
loadingIcon: {
|
|
marginBottom: '10px',
|
|
color: '#3B82F6'
|
|
},
|
|
|
|
dataContainer: {
|
|
marginTop: '20px'
|
|
},
|
|
|
|
dataHeader: {
|
|
marginBottom: '16px',
|
|
padding: '12px',
|
|
backgroundColor: '#F3F4F6',
|
|
borderRadius: '6px'
|
|
},
|
|
|
|
dataCount: {
|
|
margin: 0,
|
|
fontWeight: '600',
|
|
color: '#374151'
|
|
},
|
|
|
|
tableContainer: {
|
|
overflowX: 'auto',
|
|
border: '1px solid #E5E7EB',
|
|
borderRadius: '8px'
|
|
},
|
|
|
|
table: {
|
|
width: '100%',
|
|
borderCollapse: 'collapse'
|
|
},
|
|
|
|
tableHeaderRow: {
|
|
backgroundColor: '#F9FAFB'
|
|
},
|
|
|
|
tableHeader: {
|
|
padding: '12px',
|
|
textAlign: 'left',
|
|
fontWeight: '600',
|
|
color: '#374151',
|
|
borderBottom: '1px solid #E5E7EB'
|
|
},
|
|
|
|
tableRow: {
|
|
borderBottom: '1px solid #F3F4F6'
|
|
},
|
|
|
|
tableCell: {
|
|
padding: '12px',
|
|
color: '#6B7280'
|
|
},
|
|
|
|
statusBadge: {
|
|
padding: '4px 8px',
|
|
borderRadius: '12px',
|
|
fontSize: '12px',
|
|
fontWeight: '500'
|
|
},
|
|
};
|
|
|
|
return (
|
|
<div style={styles.container}>
|
|
{/* Header */}
|
|
<div style={styles.header}>
|
|
<div style={styles.headerContent}>
|
|
<div>
|
|
<h1 style={styles.title}>FastAPI Dashboard</h1>
|
|
<p style={styles.subtitle}>Manage your backend data and monitor API status</p>
|
|
</div>
|
|
<div style={styles.statusContainer}>
|
|
{getStatusIcon()}
|
|
<span>{getStatusText()}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Main Content */}
|
|
<div style={styles.mainContent}>
|
|
{/* Connection Section */}
|
|
<div style={styles.card}>
|
|
<h2 style={styles.cardTitle}>API Connection</h2>
|
|
<div style={styles.connectionForm}>
|
|
<input
|
|
type="text"
|
|
value={apiUrl}
|
|
onChange={(e) => setApiUrl(e.target.value)}
|
|
style={styles.input}
|
|
placeholder="Enter API URL"
|
|
/>
|
|
<button
|
|
onClick={testConnection}
|
|
disabled={isLoading}
|
|
style={{
|
|
...styles.button,
|
|
...(isLoading ? styles.buttonDisabled : {}),
|
|
':hover': { backgroundColor: '#1d4ed8' }
|
|
}}
|
|
onMouseEnter={(e) => {
|
|
if (!isLoading) e.target.style.backgroundColor = '#1d4ed8';
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
if (!isLoading) e.target.style.backgroundColor = '#2563eb';
|
|
}}
|
|
>
|
|
<RefreshCw size={16} style={isLoading ? { animation: 'spin 1s linear infinite' } : {}} />
|
|
Test Connection
|
|
</button>
|
|
</div>
|
|
{connectionStatus === 'error' && (
|
|
<div style={styles.errorAlert}>
|
|
<XCircle size={20} style={{ color: '#dc2626' }} />
|
|
<span style={styles.errorText}>NetworkError when attempting to fetch resource.</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Navigation Tabs */}
|
|
<div style={styles.tabs}>
|
|
<div style={styles.tabsNav}>
|
|
<button
|
|
onClick={() => setActiveTab('dashboard')}
|
|
style={{
|
|
...styles.tab,
|
|
...(activeTab === 'dashboard' ? styles.tabActive : styles.tabInactive)
|
|
}}
|
|
>
|
|
Dashboard
|
|
</button>
|
|
<button
|
|
onClick={() => setActiveTab('data')}
|
|
style={{
|
|
...styles.tab,
|
|
...(activeTab === 'data' ? styles.tabActive : styles.tabInactive)
|
|
}}
|
|
>
|
|
Data
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Stats Grid */}
|
|
<div style={styles.statsGrid}>
|
|
{/* API Status */}
|
|
<div style={styles.statCard}>
|
|
<div style={styles.statContent}>
|
|
<p style={styles.statLabel}>API Status</p>
|
|
<p style={styles.statValue}>
|
|
{connectionStatus === 'connected' ? 'Online' : 'Offline'}
|
|
</p>
|
|
</div>
|
|
<div style={{...styles.statIcon, ...styles.iconOrange}}>
|
|
<Server size={24} />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Active Users */}
|
|
<div style={styles.statCard}>
|
|
<div style={styles.statContent}>
|
|
<p style={styles.statLabel}>Active Users</p>
|
|
<p style={styles.statValue}>1</p>
|
|
</div>
|
|
<div style={{...styles.statIcon, ...styles.iconGreen}}>
|
|
<Users size={24} />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Activity */}
|
|
<div style={styles.statCard}>
|
|
<div style={styles.statContent}>
|
|
<p style={styles.statLabel}>Activity</p>
|
|
<p style={styles.statValue}>Live</p>
|
|
</div>
|
|
<div style={{...styles.statIcon, ...styles.iconPurple}}>
|
|
<Activity size={24} />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-gradient-to-r from-green-500 to-green-600 p-6 rounded-xl text-white">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-green-100">Active Members</p>
|
|
<p className="text-2xl font-bold">{activeMembersCount}</p>
|
|
</div>
|
|
<Users className="w-8 h-8 text-green-200" />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-gradient-to-r from-yellow-500 to-yellow-600 p-6 rounded-xl text-white">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-yellow-100">Inactive Members</p>
|
|
<p className="text-2xl font-bold">{inactiveMembersCount}</p>
|
|
</div>
|
|
<Users className="w-8 h-8 text-yellow-200" />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-gradient-to-r from-red-500 to-red-600 p-6 rounded-xl text-white">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<p className="text-red-100">Deactivated Members</p>
|
|
<p className="text-2xl font-bold">{deactivatedMembersCount}</p>
|
|
</div>
|
|
<Users className="w-8 h-8 text-red-200" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Quick Actions */}
|
|
<div style={styles.card}>
|
|
<h2 style={styles.cardTitle}>Quick Actions</h2>
|
|
<div style={styles.actionButtons}>
|
|
<button
|
|
style={{...styles.actionButton, ...styles.actionButtonGray}}
|
|
onMouseEnter={(e) => e.target.style.backgroundColor = '#e5e7eb'}
|
|
onMouseLeave={(e) => e.target.style.backgroundColor = '#f3f4f6'}
|
|
>
|
|
<Eye size={16} />
|
|
View Data
|
|
</button>
|
|
<button
|
|
style={{...styles.actionButton, ...styles.actionButtonGreen}}
|
|
onMouseEnter={(e) => e.target.style.backgroundColor = '#bbf7d0'}
|
|
onMouseLeave={(e) => e.target.style.backgroundColor = '#dcfce7'}
|
|
>
|
|
<Plus size={16} />
|
|
Add Item
|
|
</button>
|
|
<button
|
|
style={{...styles.actionButton, ...styles.actionButtonBlue}}
|
|
onMouseEnter={(e) => e.target.style.backgroundColor = '#bfdbfe'}
|
|
onMouseLeave={(e) => e.target.style.backgroundColor = '#dbeafe'}
|
|
>
|
|
<RefreshCw size={16} />
|
|
Refresh
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Content based on active tab */}
|
|
{activeTab === 'data' && (
|
|
<div style={styles.card}>
|
|
<div style={styles.cardHeader}>
|
|
<h2 style={styles.cardTitle}>Data Management</h2>
|
|
<button
|
|
onClick={loadMembers}
|
|
style={styles.refreshButton}
|
|
disabled={isLoading}
|
|
>
|
|
<RefreshCw size={16} style={{ marginRight: '8px' }} />
|
|
{isLoading ? 'Loading...' : 'Load Members'}
|
|
</button>
|
|
</div>
|
|
|
|
{isLoading ? (
|
|
<div style={styles.loadingState}>
|
|
<RefreshCw size={24} style={{ ...styles.loadingIcon, animation: 'spin 1s linear infinite' }} />
|
|
<p>Loading members...</p>
|
|
</div>
|
|
) : members.length === 0 ? (
|
|
<div style={styles.emptyState}>
|
|
<Database size={48} style={styles.emptyStateIcon} />
|
|
<p style={styles.emptyStateText}>No members found. Click "Load Members" to fetch data.</p>
|
|
</div>
|
|
) : (
|
|
<div style={styles.dataContainer}>
|
|
<div style={styles.dataHeader}>
|
|
<p style={styles.dataCount}>Total Members: {members.length}</p>
|
|
</div>
|
|
|
|
<div style={styles.tableContainer}>
|
|
<table style={styles.table}>
|
|
<thead>
|
|
<tr style={styles.tableHeaderRow}>
|
|
<th style={styles.tableHeader}>ID</th>
|
|
<th style={styles.tableHeader}>Name</th>
|
|
<th style={styles.tableHeader}>Email</th>
|
|
<th style={styles.tableHeader}>Active</th>
|
|
<th style={styles.tableHeader}>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{members.map((member, index) => (
|
|
<tr key={member.id || index} style={styles.tableRow}>
|
|
<td style={styles.tableCell}>{member.id}</td>
|
|
<td style={styles.tableCell}>{getFullName(member)}</td>
|
|
<td style={styles.tableCell}>{member.member_email || 'N/A'}</td>
|
|
<td style={styles.tableCell}>
|
|
<span style={{
|
|
...styles.statusBadge,
|
|
backgroundColor: getMemberStateColor(member.current_state.state_code),
|
|
color: 'white'
|
|
}}>
|
|
{member.current_state.state_name}
|
|
</span>
|
|
</td>
|
|
<td style={styles.tableCell}>
|
|
<button style={styles.actionButton}>
|
|
<Edit3 size={14} />
|
|
</button>
|
|
<button style={styles.actionButton}>
|
|
<Trash2 size={14} />
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
</div>
|
|
|
|
<style>
|
|
{`
|
|
@keyframes spin {
|
|
from { transform: rotate(0deg); }
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
`}
|
|
</style>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Dashboard; |