Upload New File

This commit is contained in:
Administrator
2026-04-30 18:26:22 +00:00
parent 0b87546960
commit 96c0067ef1
+172
View File
@@ -0,0 +1,172 @@
import { useState, useEffect, useCallback } from 'react'
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'
}
const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? 'https://api.rentflow.com/api/v1'
async function apiFetch<T>(
path: string,
options?: RequestInit
): Promise<T> {
const res = await fetch(`${API_BASE}${path}`, {
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
...options,
})
const json = await res.json()
if (!res.ok) {
const err = new Error(json.message ?? 'Request failed') as any
err.code = json.error
err.statusCode = res.status
throw err
}
return json.data as T
}
// ─── useTeam ─────────────────────────────────────────────────
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 fetchMembers = 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(() => { fetchMembers() }, [fetchMembers])
// ── Invite ─────────────────────────────────────────────────
const invite = useCallback(async (payload: InvitePayload) => {
const result = await apiFetch<{ employee: TeamMember; invitationId: string }>(
'/team/invite',
{ method: 'POST', body: JSON.stringify(payload) }
)
// Optimistically add the pending member
setMembers((prev) => [...prev, result.employee])
setStats((prev) => ({
...prev,
total: prev.total + 1,
pending: prev.pending + 1,
}))
return result
}, [])
// ── Update role ────────────────────────────────────────────
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) =>
prev.map((m) => (m.id === memberId ? { ...m, role: updated.role } : m))
)
return updated
}, [])
// ── Deactivate ─────────────────────────────────────────────
const deactivate = useCallback(async (memberId: string) => {
const updated = await apiFetch<TeamMember>(`/team/${memberId}/deactivate`, {
method: 'POST',
})
setMembers((prev) =>
prev.map((m) => (m.id === memberId ? { ...m, isActive: false } : m))
)
setStats((prev) => ({ ...prev, active: prev.active - 1, inactive: prev.inactive + 1 }))
return updated
}, [])
// ── Reactivate ─────────────────────────────────────────────
const reactivate = useCallback(async (memberId: string) => {
const updated = await apiFetch<TeamMember>(`/team/${memberId}/reactivate`, {
method: 'POST',
})
setMembers((prev) =>
prev.map((m) => (m.id === memberId ? { ...m, isActive: true } : m))
)
setStats((prev) => ({ ...prev, active: prev.active + 1, inactive: prev.inactive - 1 }))
return updated
}, [])
// ── Remove ─────────────────────────────────────────────────
const remove = useCallback(async (memberId: string) => {
await apiFetch<{ success: boolean }>(`/team/${memberId}`, {
method: 'DELETE',
})
const target = members.find((m) => m.id === memberId)
setMembers((prev) => prev.filter((m) => m.id !== memberId))
setStats((prev) => ({
...prev,
total: prev.total - 1,
active: target?.isActive ? prev.active - 1 : prev.active,
pending: target?.invitationStatus === 'pending' ? prev.pending - 1 : prev.pending,
inactive: !target?.isActive && target?.invitationStatus === 'accepted'
? prev.inactive - 1
: prev.inactive,
}))
}, [members])
return {
members,
stats,
loading,
error,
refetch: fetchMembers,
invite,
updateRole,
deactivate,
reactivate,
remove,
}
}