282 lines
7.6 KiB
TypeScript
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
|
|
};
|
|
}
|