271 lines
7.7 KiB
React
271 lines
7.7 KiB
React
|
|
import { useEffect, useMemo, useState } from 'react';
|
||
|
|
import { DataPanel } from '../components/DataPanel';
|
||
|
|
import { ManagementLayout } from '../components/ManagementLayout';
|
||
|
|
import { ManagementTable } from '../components/ManagementTable';
|
||
|
|
import { MetricGrid } from '../components/MetricGrid';
|
||
|
|
import { adminMetrics, aiContentRows, areaRows, userRows } from '../services/managementMocks';
|
||
|
|
import { getAccessOptions, getAccessUsers, updateUserAccess } from '../services/adminAccessService';
|
||
|
|
import { useViewport } from '../../../shared/hooks/useViewport';
|
||
|
|
|
||
|
|
const areaColumns = [
|
||
|
|
{ key: 'name', label: 'Area' },
|
||
|
|
{ key: 'owner', label: 'Responsavel' },
|
||
|
|
{ key: 'members', label: 'Usuarios' },
|
||
|
|
{ key: 'status', label: 'Status' },
|
||
|
|
];
|
||
|
|
|
||
|
|
const contentColumns = [
|
||
|
|
{ key: 'title', label: 'Conteudo' },
|
||
|
|
{ key: 'area', label: 'Area' },
|
||
|
|
{ key: 'status', label: 'Status' },
|
||
|
|
{ key: 'updatedAt', label: 'Atualizado' },
|
||
|
|
];
|
||
|
|
|
||
|
|
const selectStyle = {
|
||
|
|
width: '100%',
|
||
|
|
border: '1px solid var(--color-border)',
|
||
|
|
borderRadius: '14px',
|
||
|
|
padding: '0.75rem 0.85rem',
|
||
|
|
background: '#fff',
|
||
|
|
color: 'var(--color-text)',
|
||
|
|
fontWeight: 600,
|
||
|
|
};
|
||
|
|
|
||
|
|
function mapMockUsers() {
|
||
|
|
return userRows.map((user) => ({
|
||
|
|
id: user.id,
|
||
|
|
nome: user.name,
|
||
|
|
email: user.email,
|
||
|
|
perfilPrincipal: { id: user.role, nome: user.role },
|
||
|
|
areaPrincipal: { id: user.area, nome: user.area },
|
||
|
|
accessStatus: 'assigned',
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
|
||
|
|
export function AdminPage() {
|
||
|
|
const { isDesktop, isMobile } = useViewport();
|
||
|
|
const [users, setUsers] = useState(mapMockUsers);
|
||
|
|
const [profiles, setProfiles] = useState([]);
|
||
|
|
const [areas, setAreas] = useState([]);
|
||
|
|
const [isLoadingAccess, setIsLoadingAccess] = useState(true);
|
||
|
|
const [accessError, setAccessError] = useState('');
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
let isMounted = true;
|
||
|
|
|
||
|
|
async function loadAccessData() {
|
||
|
|
try {
|
||
|
|
const [options, accessUsers] = await Promise.all([getAccessOptions(), getAccessUsers()]);
|
||
|
|
|
||
|
|
if (!isMounted) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
setProfiles(options.profiles || []);
|
||
|
|
setAreas(options.areas || []);
|
||
|
|
setUsers(accessUsers || []);
|
||
|
|
setAccessError('');
|
||
|
|
} catch {
|
||
|
|
if (isMounted) {
|
||
|
|
setAccessError('Backend indisponivel. Exibindo dados demonstrativos.');
|
||
|
|
}
|
||
|
|
} finally {
|
||
|
|
if (isMounted) {
|
||
|
|
setIsLoadingAccess(false);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
loadAccessData();
|
||
|
|
|
||
|
|
return () => {
|
||
|
|
isMounted = false;
|
||
|
|
};
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
async function handleAccessChange(user, field, value) {
|
||
|
|
const currentPerfilId = user.perfilPrincipal?.id || null;
|
||
|
|
const currentAreaId = user.areaPrincipal?.id || null;
|
||
|
|
const nextAccess = {
|
||
|
|
perfilId: field === 'perfil' ? Number(value) || null : currentPerfilId,
|
||
|
|
areaId: field === 'area' ? Number(value) || null : currentAreaId,
|
||
|
|
};
|
||
|
|
|
||
|
|
setUsers((current) =>
|
||
|
|
current.map((item) =>
|
||
|
|
item.id === user.id
|
||
|
|
? {
|
||
|
|
...item,
|
||
|
|
perfilPrincipal:
|
||
|
|
profiles.find((profile) => profile.id === nextAccess.perfilId) || null,
|
||
|
|
areaPrincipal: areas.find((area) => area.id === nextAccess.areaId) || null,
|
||
|
|
accessStatus: nextAccess.perfilId && nextAccess.areaId ? 'assigned' : 'unassigned',
|
||
|
|
}
|
||
|
|
: item,
|
||
|
|
),
|
||
|
|
);
|
||
|
|
|
||
|
|
try {
|
||
|
|
const updatedUser = await updateUserAccess(user.id, nextAccess);
|
||
|
|
|
||
|
|
if (updatedUser) {
|
||
|
|
setUsers((current) =>
|
||
|
|
current.map((item) => (item.id === updatedUser.id ? updatedUser : item)),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
setAccessError('');
|
||
|
|
} catch {
|
||
|
|
setAccessError('Nao foi possivel salvar a atribuicao. Confira o backend.');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const userColumns = useMemo(
|
||
|
|
() => [
|
||
|
|
{
|
||
|
|
key: 'nome',
|
||
|
|
label: 'Usuario',
|
||
|
|
render: (row) => (
|
||
|
|
<div>
|
||
|
|
<strong style={{ display: 'block' }}>{row.nome}</strong>
|
||
|
|
<span style={{ color: 'var(--color-text-soft)', fontSize: '0.9rem' }}>
|
||
|
|
{row.email || 'Sem email'}
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
key: 'perfil',
|
||
|
|
label: 'Perfil',
|
||
|
|
render: (row) =>
|
||
|
|
profiles.length ? (
|
||
|
|
<select
|
||
|
|
value={row.perfilPrincipal?.id || ''}
|
||
|
|
onChange={(event) => handleAccessChange(row, 'perfil', event.target.value)}
|
||
|
|
style={selectStyle}
|
||
|
|
>
|
||
|
|
<option value="">Sem perfil</option>
|
||
|
|
{profiles.map((profile) => (
|
||
|
|
<option key={profile.id} value={profile.id}>
|
||
|
|
{profile.nome}
|
||
|
|
</option>
|
||
|
|
))}
|
||
|
|
</select>
|
||
|
|
) : (
|
||
|
|
<span>{row.perfilPrincipal?.nome || 'Sem perfil'}</span>
|
||
|
|
),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
key: 'area',
|
||
|
|
label: 'Area',
|
||
|
|
render: (row) =>
|
||
|
|
areas.length ? (
|
||
|
|
<select
|
||
|
|
value={row.areaPrincipal?.id || ''}
|
||
|
|
onChange={(event) => handleAccessChange(row, 'area', event.target.value)}
|
||
|
|
style={selectStyle}
|
||
|
|
>
|
||
|
|
<option value="">Sem area</option>
|
||
|
|
{areas.map((area) => (
|
||
|
|
<option key={area.id} value={area.id}>
|
||
|
|
{area.nome}
|
||
|
|
</option>
|
||
|
|
))}
|
||
|
|
</select>
|
||
|
|
) : (
|
||
|
|
<span>{row.areaPrincipal?.nome || 'Sem area'}</span>
|
||
|
|
),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
key: 'status',
|
||
|
|
label: 'Status',
|
||
|
|
render: (row) => {
|
||
|
|
const isAssigned = row.accessStatus === 'assigned';
|
||
|
|
|
||
|
|
return (
|
||
|
|
<span
|
||
|
|
style={{
|
||
|
|
width: 'fit-content',
|
||
|
|
borderRadius: 999,
|
||
|
|
padding: '0.25rem 0.6rem',
|
||
|
|
background: isAssigned ? 'rgba(0, 164, 183, 0.1)' : 'rgba(229, 162, 42, 0.16)',
|
||
|
|
color: isAssigned ? 'var(--color-primary)' : '#8a5a00',
|
||
|
|
fontWeight: 700,
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
{isAssigned ? 'Atribuido' : 'Pendente'}
|
||
|
|
</span>
|
||
|
|
);
|
||
|
|
},
|
||
|
|
},
|
||
|
|
],
|
||
|
|
[areas, profiles],
|
||
|
|
);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<ManagementLayout
|
||
|
|
title="Painel administrativo"
|
||
|
|
subtitle="Controle de usuarios, perfis, areas e base de conteudo para IA."
|
||
|
|
activeSection="admin"
|
||
|
|
profileLabel="Lucas Admin"
|
||
|
|
initials="LA"
|
||
|
|
isDesktop={isDesktop}
|
||
|
|
isMobile={isMobile}
|
||
|
|
>
|
||
|
|
<MetricGrid metrics={adminMetrics} />
|
||
|
|
|
||
|
|
<div
|
||
|
|
style={{
|
||
|
|
display: 'grid',
|
||
|
|
gridTemplateColumns: isDesktop ? 'minmax(0, 1.2fr) minmax(320px, 0.8fr)' : '1fr',
|
||
|
|
gap: '1rem',
|
||
|
|
alignItems: 'start',
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<DataPanel
|
||
|
|
title="Usuarios e niveis de acesso"
|
||
|
|
description={
|
||
|
|
isLoadingAccess
|
||
|
|
? 'Carregando usuarios do banco...'
|
||
|
|
: accessError || 'Gerencie perfil e area principal dos usuarios autenticados.'
|
||
|
|
}
|
||
|
|
actionLabel="Adicionar usuario"
|
||
|
|
>
|
||
|
|
<ManagementTable
|
||
|
|
columns={userColumns}
|
||
|
|
rows={users}
|
||
|
|
getRowId={(row) => row.id}
|
||
|
|
isMobile={isMobile}
|
||
|
|
/>
|
||
|
|
</DataPanel>
|
||
|
|
|
||
|
|
<DataPanel
|
||
|
|
title="Areas"
|
||
|
|
description="Areas operacionais e seus responsaveis."
|
||
|
|
actionLabel="Nova area"
|
||
|
|
>
|
||
|
|
<ManagementTable
|
||
|
|
columns={areaColumns}
|
||
|
|
rows={areaRows}
|
||
|
|
getRowId={(row) => row.id}
|
||
|
|
isMobile={isMobile}
|
||
|
|
/>
|
||
|
|
</DataPanel>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<DataPanel
|
||
|
|
title="Conteudo para IA"
|
||
|
|
description="Entradas mockadas para alimentar a base de conhecimento."
|
||
|
|
actionLabel="Adicionar conteudo"
|
||
|
|
>
|
||
|
|
<ManagementTable
|
||
|
|
columns={contentColumns}
|
||
|
|
rows={aiContentRows}
|
||
|
|
getRowId={(row) => row.id}
|
||
|
|
isMobile={isMobile}
|
||
|
|
/>
|
||
|
|
</DataPanel>
|
||
|
|
</ManagementLayout>
|
||
|
|
);
|
||
|
|
}
|