diff --git a/convex/machines.ts b/convex/machines.ts index c5bcfb4..a5014a8 100644 --- a/convex/machines.ts +++ b/convex/machines.ts @@ -1089,9 +1089,9 @@ export const linkUser = mutation({ .first() if (!user) throw new ConvexError("Usuário não encontrado") - const current = new Set((machine.linkedUserIds ?? []).map((id) => id.id ?? id)) + const current = new Set>(machine.linkedUserIds ?? []) current.add(user._id) - await ctx.db.patch(machine._id, { linkedUserIds: Array.from(current) as any, updatedAt: Date.now() }) + await ctx.db.patch(machine._id, { linkedUserIds: Array.from(current), updatedAt: Date.now() }) return { ok: true } }, }) diff --git a/src/components/admin/admin-users-manager.tsx b/src/components/admin/admin-users-manager.tsx index 754d404..1a21fdd 100644 --- a/src/components/admin/admin-users-manager.tsx +++ b/src/components/admin/admin-users-manager.tsx @@ -246,19 +246,7 @@ export function AdminUsersManager({ initialUsers, initialInvites, roleOptions, d const [teamSelection, setTeamSelection] = useState>(new Set()) const [isBulkDeletingTeam, setIsBulkDeletingTeam] = useState(false) const [bulkDeleteTeamOpen, setBulkDeleteTeamOpen] = useState(false) - // Usuários - const [peopleSearch, setPeopleSearch] = useState("") - const [peopleRoleFilter, setPeopleRoleFilter] = useState<"all" | "manager" | "collaborator">("all") - const [peopleCompanyFilter, setPeopleCompanyFilter] = useState("all") - const [peopleTenantFilter, setPeopleTenantFilter] = useState("all") - const [peopleSelection, setPeopleSelection] = useState>(new Set()) - const [isBulkDeletingPeople, setIsBulkDeletingPeople] = useState(false) - const [bulkDeletePeopleOpen, setBulkDeletePeopleOpen] = useState(false) - const [machineSearch, setMachineSearch] = useState("") - const [machinePersonaFilter, setMachinePersonaFilter] = useState<"all" | "manager" | "collaborator" | "unassigned">("all") - const [machineSelection, setMachineSelection] = useState>(new Set()) - const [isBulkDeletingMachines, setIsBulkDeletingMachines] = useState(false) - const [bulkDeleteMachinesOpen, setBulkDeleteMachinesOpen] = useState(false) + // Removidos filtros antigos de Pessoas/Máquinas (agora unificado) // Unificado (pessoas + máquinas) const [usersSearch, setUsersSearch] = useState("") const [usersTypeFilter, setUsersTypeFilter] = useState<"all" | "people" | "machines">("all") @@ -279,10 +267,45 @@ export function AdminUsersManager({ initialUsers, initialInvites, roleOptions, d const [createPassword, setCreatePassword] = useState(null) // Máquinas (para listar vínculos por usuário) + type MachinesListItem = { + id: string + hostname?: string + assignedUserEmail?: string | null + metadata?: unknown + linkedUsers?: Array<{ id: string; email: string; name: string }> + } const machinesList = useQuery( convexUserId ? api.machines.listByTenant : "skip", convexUserId ? { tenantId: defaultTenantId, includeMetadata: true } : ("skip" as const) - ) as Array<{ id: string; hostname?: string; assignedUserEmail?: string | null; metadata?: unknown }> | undefined + ) as MachinesListItem[] | undefined + + const machinesByUserEmail = useMemo(() => { + const map = new Map>() + ;(machinesList ?? []).forEach((m) => { + const push = (email?: string | null) => { + const e = (email ?? '').toLowerCase() + if (!e) return + const arr = map.get(e) ?? [] + arr.push({ id: m.id, hostname: m.hostname }) + map.set(e, arr) + } + push(m.assignedUserEmail) + // metadata collaborator + if (m.metadata && typeof m.metadata === 'object') { + const rec = m.metadata as Record + const c = rec['collaborator'] + if (c && typeof c === 'object') { + const base = c as Record + if (typeof base.email === 'string') push(base.email) + } + } + // linked users + if (Array.isArray(m.linkedUsers)) { + m.linkedUsers.forEach((lu) => push(lu.email)) + } + }) + return map + }, [machinesList]) // Options of tenants present in dataset for filtering const tenantOptions = useMemo(() => { @@ -311,43 +334,9 @@ export function AdminUsersManager({ initialUsers, initialInvites, roleOptions, d }) }, [teamUsers, teamSearch, teamRoleFilter, teamCompanyFilter, teamTenantFilter, defaultTenantId]) - const filteredPeopleUsers = useMemo(() => { - const term = peopleSearch.trim().toLowerCase() - return peopleUsers.filter((user) => { - const role = coerceRole(user.role) - if (peopleRoleFilter !== "all" && role !== peopleRoleFilter) return false - if (peopleCompanyFilter !== "all" && user.companyId !== peopleCompanyFilter) return false - if (peopleTenantFilter !== "all" && (user.tenantId ?? defaultTenantId) !== peopleTenantFilter) return false - if (!term) return true - const haystack = [ - user.name ?? "", - user.email ?? "", - user.companyName ?? "", - formatRole(user.role), - ] - .join(" ") - .toLowerCase() - return haystack.includes(term) - }) - }, [peopleUsers, peopleSearch, peopleRoleFilter, peopleCompanyFilter, peopleTenantFilter, defaultTenantId]) + // Removido: lista específica de Pessoas (uso substituído pelo unificado) - const filteredMachineUsers = useMemo(() => { - const term = machineSearch.trim().toLowerCase() - return machineUsers.filter((user) => { - const persona = (user.machinePersona ?? "unassigned").toLowerCase() - if (machinePersonaFilter !== "all") { - if (machinePersonaFilter === "unassigned" && persona !== "unassigned") return false - if (machinePersonaFilter !== "unassigned" && persona !== machinePersonaFilter) return false - } - if (!term) return true - return ( - (user.name ?? "").toLowerCase().includes(term) || - user.email.toLowerCase().includes(term) || - persona.includes(term) || - (extractMachineId(user.email) ?? "").toLowerCase().includes(term) - ) - }) - }, [machineUsers, machinePersonaFilter, machineSearch]) + // Removido: filtro específico de agentes (uso substituído pelo unificado) const combinedBaseUsers = useMemo(() => { if (usersTypeFilter === "people") return peopleUsers @@ -426,8 +415,8 @@ export function AdminUsersManager({ initialUsers, initialInvites, roleOptions, d if (typeof base.email === "string") collaboratorEmail = base.email.toLowerCase() } } - const linked = Array.isArray((m as any).linkedUsers) - ? ((m as any).linkedUsers as Array<{ email?: string }>).some((lu) => (lu.email ?? '').toLowerCase() === email) + const linked = Array.isArray(m.linkedUsers) + ? m.linkedUsers.some((lu) => (lu.email ?? '').toLowerCase() === email) : false if (assigned === email || (collaboratorEmail && collaboratorEmail === email) || linked) { results.push({ id: m.id, hostname: m.hostname }) @@ -714,11 +703,9 @@ export function AdminUsersManager({ initialUsers, initialInvites, roleOptions, d const allTeamSelected = selectedTeamUsers.length > 0 && selectedTeamUsers.length === filteredTeamUsers.length const someTeamSelected = selectedTeamUsers.length > 0 && !allTeamSelected - const selectedPeopleUsers = useMemo(() => filteredPeopleUsers.filter((u) => peopleSelection.has(u.id)), [filteredPeopleUsers, peopleSelection]) - const allPeopleSelected = selectedPeopleUsers.length > 0 && selectedPeopleUsers.length === filteredPeopleUsers.length - const somePeopleSelected = selectedPeopleUsers.length > 0 && !allPeopleSelected + // Removido: seleção específica de Pessoas (uso substituído pelo unificado) - const selectedMachineUsers = useMemo(() => filteredMachineUsers.filter((u) => machineSelection.has(u.id)), [filteredMachineUsers, machineSelection]) + // Removido: seleção específica de Máquinas (uso substituído pelo unificado) const [inviteSelection, setInviteSelection] = useState>(new Set()) const selectedInvites = useMemo(() => invites.filter((i) => inviteSelection.has(i.id)), [invites, inviteSelection]) @@ -730,12 +717,7 @@ export function AdminUsersManager({ initialUsers, initialInvites, roleOptions, d function toggleTeamSelectAll(checked: boolean) { setTeamSelection(checked ? new Set(filteredTeamUsers.map((u) => u.id)) : new Set()) } - function togglePeopleSelectAll(checked: boolean) { - setPeopleSelection(checked ? new Set(filteredPeopleUsers.map((u) => u.id)) : new Set()) - } - function toggleMachinesSelectAll(checked: boolean) { - setMachineSelection(checked ? new Set(filteredMachineUsers.map((u) => u.id)) : new Set()) - } + // Removidos: toggles de seleção específicos (uso substituído pelo unificado) function toggleInvitesSelectAll(checked: boolean) { setInviteSelection(checked ? new Set(invites.map((i) => i.id)) : new Set()) } @@ -1063,6 +1045,7 @@ async function handleDeleteUser() { E-mail Papel Empresa + Máquinas Espaço Criado em Ações @@ -1091,6 +1074,14 @@ async function handleDeleteUser() { {user.email} {formatRole(user.role)} {user.companyName ?? "—"} + + {(() => { + const list = machinesByUserEmail.get((user.email ?? '').toLowerCase()) ?? [] + return list.length > 0 ? ( + {list.length} {list.length === 1 ? 'máquina' : 'máquinas'} + ) : '—' + })()} + {formatTenantLabel(user.tenantId, defaultTenantId)} {formatDate(user.createdAt)} @@ -2012,93 +2003,7 @@ async function handleDeleteUser() { - - - - Remover usuários selecionados - Os usuários perderão o acesso imediatamente. - -
- {selectedPeopleUsers.slice(0, 5).map((u) => ( -
- {u.name || u.email} — {u.email} -
- ))} - {selectedPeopleUsers.length > 5 ? ( -
+ {selectedPeopleUsers.length - 5} outros
- ) : null} -
- - - - -
-
- - - - - Remover agentes selecionados - As máquinas serão desconectadas e precisarão de novo provisionamento. - -
- {selectedMachineUsers.slice(0, 5).map((u) => ( -
- {u.name || u.email} — {u.email} -
- ))} - {selectedMachineUsers.length > 5 ? ( -
+ {selectedMachineUsers.length - 5} outros
- ) : null} -
- - - - -
-
+ {/* Dialogs antigos removidos: ações em massa agora são unificadas no diálogo abaixo */} diff --git a/src/components/admin/machines/admin-machines-overview.tsx b/src/components/admin/machines/admin-machines-overview.tsx index c838a9d..d4d1368 100644 --- a/src/components/admin/machines/admin-machines-overview.tsx +++ b/src/components/admin/machines/admin-machines-overview.tsx @@ -1934,7 +1934,7 @@ export function MachineDetails({ machine }: MachineDetailsProps) { > Adicionar vínculo - gerenciar usuários + Gerenciar usuários @@ -3287,7 +3287,7 @@ function MachineCard({ machine, companyName }: { machine: MachinesQueryItem; com Usuário vinculado: {collaborator.name ? `${collaborator.name} · ` : ""}{collaborator.email} - gerenciar usuários + Gerenciar usuários ) : null} {!isActive ? (