lad-website/frontend/src/hooks/useMembers.ts

282 lines
7.6 KiB
TypeScript

import { useState, useEffect, useCallback } from 'react';
import axios from 'axios';
// Define Member interface
export interface Member {
_id: string;
firstName: string;
lastName: string;
email: string;
phone?: string;
address?: {
street?: string;
city?: string;
state?: string;
zip?: string;
};
membershipType: 'regular' | 'lifetime' | 'honorary';
joinDate: string;
expirationDate?: string;
status: 'active' | 'expired' | 'pending';
notificationPreference?: 'email' | 'sms' | 'both';
lastRenewalDate?: string;
boardMember: boolean;
boardPosition?: string;
emergencyContact?: {
name: string;
relationship: string;
phone: string;
};
createdAt: string;
updatedAt: string;
}
// Define filter interface
export interface MemberFilter {
status?: string;
type?: string;
search?: string;
boardMember?: boolean;
page?: number;
limit?: number;
}
// Define pagination interface
export interface Pagination {
total: number;
page: number;
limit: number;
pages: number;
}
// Define analytics interface
export interface MemberAnalytics {
statusCounts: Record<string, number>;
typeCounts: Record<string, number>;
boardMemberCount: number;
expiringCount: number;
recentJoinsCount: number;
totalMembers: number;
}
// Define bulk action interface
export interface BulkActionResult {
message: string;
affected: number;
}
// Define hook return interface
export interface UseMembersReturn {
members: Member[];
loading: boolean;
error: Error | null;
pagination: Pagination | null;
analytics: MemberAnalytics | null;
fetchMembers: (filters?: MemberFilter) => Promise<void>;
getMemberById: (id: string) => Promise<Member | null>;
createMember: (memberData: Omit<Member, '_id' | 'createdAt' | 'updatedAt'>) => Promise<Member | null>;
updateMember: (id: string, memberData: Partial<Member>) => Promise<Member | null>;
deleteMember: (id: string) => Promise<boolean>;
bulkAction: (action: string, memberIds: string[]) => Promise<BulkActionResult | null>;
fetchAnalytics: () => Promise<MemberAnalytics | null>;
}
// API base URL
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000/api';
/**
* Hook for managing members data
*/
export function useMembers(): UseMembersReturn {
const [members, setMembers] = useState<Member[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<Error | null>(null);
const [pagination, setPagination] = useState<Pagination | null>(null);
const [analytics, setAnalytics] = useState<MemberAnalytics | null>(null);
/**
* Fetch members with optional filtering
*/
const fetchMembers = useCallback(async (filters?: MemberFilter) => {
setLoading(true);
setError(null);
try {
// Build query string from filters
const queryParams = new URLSearchParams();
if (filters) {
Object.entries(filters).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') {
queryParams.append(key, String(value));
}
});
}
const response = await axios.get(`${API_URL}/members?${queryParams.toString()}`);
setMembers(response.data.members);
setPagination(response.data.pagination);
} catch (err) {
console.error('Error fetching members:', err);
setError(err instanceof Error ? err : new Error('Failed to fetch members'));
} finally {
setLoading(false);
}
}, []);
/**
* Get a single member by ID
*/
const getMemberById = useCallback(async (id: string): Promise<Member | null> => {
try {
const response = await axios.get(`${API_URL}/members/${id}`);
return response.data.member;
} catch (err) {
console.error(`Error fetching member with ID ${id}:`, err);
return null;
}
}, []);
/**
* Create a new member
*/
const createMember = useCallback(async (memberData: Omit<Member, '_id' | 'createdAt' | 'updatedAt'>): Promise<Member | null> => {
try {
const response = await axios.post(`${API_URL}/members`, memberData, {
headers: {
'Content-Type': 'application/json',
// Add authorization header if needed
// 'Authorization': `Bearer ${token}`
}
});
// Refresh members list after creating
fetchMembers();
return response.data.member;
} catch (err) {
console.error('Error creating member:', err);
// Rethrow the error so it can be caught and handled by the component
throw err;
}
}, [fetchMembers]);
/**
* Update an existing member
*/
const updateMember = useCallback(async (id: string, memberData: Partial<Member>): Promise<Member | null> => {
try {
const response = await axios.put(`${API_URL}/members/${id}`, memberData, {
headers: {
'Content-Type': 'application/json',
// Add authorization header if needed
// 'Authorization': `Bearer ${token}`
}
});
// Update local state
setMembers(prevMembers =>
prevMembers.map(member =>
member._id === id ? { ...member, ...response.data.member } : member
)
);
return response.data.member;
} catch (err) {
console.error(`Error updating member with ID ${id}:`, err);
// Rethrow the error so it can be caught and handled by the component
throw err;
}
}, []);
/**
* Delete a member
*/
const deleteMember = useCallback(async (id: string): Promise<boolean> => {
try {
await axios.delete(`${API_URL}/members/${id}`, {
headers: {
// Add authorization header if needed
// 'Authorization': `Bearer ${token}`
}
});
// Update local state
setMembers(prevMembers => prevMembers.filter(member => member._id !== id));
return true;
} catch (err) {
console.error(`Error deleting member with ID ${id}:`, err);
return false;
}
}, []);
/**
* Perform bulk actions on members
*/
const bulkAction = useCallback(async (action: string, memberIds: string[]): Promise<BulkActionResult | null> => {
try {
const response = await axios.post(`${API_URL}/members/bulk-action`,
{ action, memberIds },
{
headers: {
'Content-Type': 'application/json',
// Add authorization header if needed
// 'Authorization': `Bearer ${token}`
}
}
);
// Refresh members list after bulk action
fetchMembers();
return {
message: response.data.message,
affected: response.data.affected
};
} catch (err) {
console.error(`Error performing bulk action ${action}:`, err);
return null;
}
}, [fetchMembers]);
/**
* Fetch member analytics
*/
const fetchAnalytics = useCallback(async (): Promise<MemberAnalytics | null> => {
try {
const response = await axios.get(`${API_URL}/members/analytics/stats`, {
headers: {
// Add authorization header if needed
// 'Authorization': `Bearer ${token}`
}
});
const analyticsData = response.data.analytics;
setAnalytics(analyticsData);
return analyticsData;
} catch (err) {
console.error('Error fetching member analytics:', err);
return null;
}
}, []);
// Load members on initial render
useEffect(() => {
fetchMembers();
}, [fetchMembers]);
return {
members,
loading,
error,
pagination,
analytics,
fetchMembers,
getMemberById,
createMember,
updateMember,
deleteMember,
bulkAction,
fetchAnalytics
};
}