Files
carmanagement/apps/dashboard/src/hooks/useTeam.ts
T
2026-06-10 00:40:19 -04:00

161 lines
4.9 KiB
TypeScript

'use client'
import { useState, useEffect, useCallback } from 'react'
import { apiFetch } from '@/lib/api'
export type EmployeeRole = 'OWNER' | 'MANAGER' | 'AGENT'
export type InvitationStatus = 'accepted' | 'pending' | 'revoked'
export interface TeamMember {
id: string
firstName: string
lastName: string
email: string
phone: string | null
role: EmployeeRole
isActive: boolean
createdAt: string
updatedAt: string
lastActiveAt: string | null
invitationStatus: InvitationStatus
}
export interface TeamStats {
total: number
active: number
pending: number
inactive: number
}
export interface InvitePayload {
firstName: string
lastName: string
email: string
role: 'MANAGER' | 'AGENT'
}
export function appendInvitedMember(members: TeamMember[], employee: TeamMember): TeamMember[] {
return [...members, employee]
}
export function incrementInvitedStats(stats: TeamStats): TeamStats {
return { ...stats, total: stats.total + 1, pending: stats.pending + 1 }
}
export function applyMemberRoleUpdate(members: TeamMember[], memberId: string, role: 'MANAGER' | 'AGENT'): TeamMember[] {
return members.map((member) => (member.id === memberId ? { ...member, role } : member))
}
export function applyMemberActivation(members: TeamMember[], memberId: string, isActive: boolean): TeamMember[] {
return members.map((member) => (member.id === memberId ? { ...member, isActive } : member))
}
export function applyActivationStats(stats: TeamStats, direction: 'deactivate' | 'reactivate'): TeamStats {
return direction === 'deactivate'
? { ...stats, active: stats.active - 1, inactive: stats.inactive + 1 }
: { ...stats, active: stats.active + 1, inactive: stats.inactive - 1 }
}
export function removeTeamMemberState(members: TeamMember[], stats: TeamStats, memberId: string): { members: TeamMember[]; stats: TeamStats } {
const target = members.find((member) => member.id === memberId)
return {
members: members.filter((member) => member.id !== memberId),
stats: {
...stats,
total: stats.total - (target ? 1 : 0),
active: target?.isActive ? stats.active - 1 : stats.active,
pending: target?.invitationStatus === 'pending' ? stats.pending - 1 : stats.pending,
inactive: !target?.isActive && target?.invitationStatus === 'accepted'
? stats.inactive - 1
: stats.inactive,
},
}
}
export function useTeam() {
const [members, setMembers] = useState<TeamMember[]>([])
const [stats, setStats] = useState<TeamStats>({ total: 0, active: 0, pending: 0, inactive: 0 })
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const refetch = useCallback(async () => {
setLoading(true)
setError(null)
try {
const [membersData, statsData] = await Promise.all([
apiFetch<TeamMember[]>('/team'),
apiFetch<TeamStats>('/team/stats'),
])
setMembers(membersData)
setStats(statsData)
} catch (err: any) {
setError(err.message)
} finally {
setLoading(false)
}
}, [])
useEffect(() => { refetch() }, [refetch])
const invite = useCallback(async (payload: InvitePayload) => {
const result = await apiFetch<{ employee: TeamMember; invitationId: string }>(
'/team/invite',
{ method: 'POST', body: JSON.stringify(payload) }
)
setMembers((prev) => appendInvitedMember(prev, result.employee))
setStats(incrementInvitedStats)
return result
}, [])
const updateRole = useCallback(async (memberId: string, role: 'MANAGER' | 'AGENT') => {
const updated = await apiFetch<TeamMember>(`/team/${memberId}/role`, {
method: 'PATCH',
body: JSON.stringify({ role }),
})
setMembers((prev) => applyMemberRoleUpdate(prev, memberId, updated.role as 'MANAGER' | 'AGENT'))
return updated
}, [])
const deactivate = useCallback(async (memberId: string) => {
const updated = await apiFetch<TeamMember>(`/team/${memberId}/deactivate`, {
method: 'POST',
})
setMembers((prev) => applyMemberActivation(prev, memberId, false))
setStats((prev) => applyActivationStats(prev, 'deactivate'))
return updated
}, [])
const reactivate = useCallback(async (memberId: string) => {
const updated = await apiFetch<TeamMember>(`/team/${memberId}/reactivate`, {
method: 'POST',
})
setMembers((prev) => applyMemberActivation(prev, memberId, true))
setStats((prev) => applyActivationStats(prev, 'reactivate'))
return updated
}, [])
const remove = useCallback(async (memberId: string) => {
await apiFetch<{ success: boolean }>(`/team/${memberId}`, {
method: 'DELETE',
})
const nextState = removeTeamMemberState(members, stats, memberId)
setMembers(nextState.members)
setStats(nextState.stats)
}, [members, stats])
return {
members,
stats,
loading,
error,
refetch,
invite,
updateRole,
deactivate,
reactivate,
remove,
}
}