Upload New File
This commit is contained in:
+172
@@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user