Plataforma operacional da TM para gestão de pessoas, reposições, contratos, estoque, projetos, chamados e rotinas operacionais em tempo real.
O TM Hub concentra processos administrativos e operacionais em uma interface responsiva. O frontend consome a API Flask do ecossistema TM Hub e recebe eventos Socket.IO para atualizar apenas o módulo afetado, sem recarregar a aplicação inteira.
| Área | Recursos |
|---|---|
| Reposições | Requisição pública e autenticada, lançamento rápido, reservas técnicas, indisponibilidade, histórico, timeline e painel ODS/KDS em tempo real. |
| Faltas | Controle, tratativa, prazo documental, lançamento manual, dashboard e regras para férias, remanejamento e posto vago. |
| Glosas | Cobertura integral ou parcial, valores, evidências, filtros, XLSX, dashboard e métrica específica da Roçada. |
| Admissões e rescisões | Vagas de substituição e aditivos, SLA em dias úteis, dashboard e controle de desligamentos. |
| Estoque e logística | Produtos, categorias, movimentações, destinatários por colaborador, locais automáticos, códigos de barras e dashboard logístico. |
| Experiências | Controle de contratos de experiências, desde a avaliação até o documento. |
| Estrutura | Filiais, departamentos, contratos, supervisor, locais, subestruturas ilimitadas, ativos e patrimônio. |
| TM Ops / Scheduler | Rotinas, vínculos por estrutura, checklists, tarefas recorrentes, workflows, Executor mobile, evidências e geolocalização. |
| Projetos | Kanban, cards, datas, membros, comentários, anexos e dashboard de projetos. |
| Chamados | Central de chamados com SLA de 24h, conversa em tempo real, responsáveis, motivos e gestão administrativa por filial. |
| Timo | Configuração de intenções, consultas analíticas, aprendizado assistido e integração com o Timo Voice Agent. |
| Dashboards | Reposições, faltas, admissões, projetos, logística, PCD, glosas, rescisões e colaboradores por departamento. |
- Autenticação JWT, requisitos de primeiro acesso e troca de senha segura.
- Permissões por tela e ação: visualizar, criar e alterar.
- Escopo obrigatório de filial; administradores e usuários Matriz podem usar o seletor global de filiais.
- Temas TM Hub, Cyberpunk e Pride com modo claro/escuro persistido.
- Componentes reutilizáveis de cabeçalho, filtros, tabelas, diálogos e indicadores.
- Datas formatadas em
pt-BR, filtros procedurais e exportações que respeitam o filtro aplicado na tela. - WebSocket por canal de domínio para atualizações pontuais de dados.
- PWA e layout responsivo para operação em desktop e mobile.
Usuário
│
├── React + PrimeReact
│ ├── Axios ───────────────────► API Flask
│ └── Socket.IO ◄──────────────► Flask-SocketIO
│ │
└── Theme, permissões e filial └── PostgreSQL + SQLAlchemy
- Node.js 20 ou superior.
- npm.
- API em produção.
git clone https://github.com/foxtec198/TMHub.git
cd TMHub
npm.cmd install
npm.cmd run devEm desenvolvimento, o frontend usa http://localhost:8590. Para o ambiente
de produção, configure VITE_SERVER com a URL pública da API antes do build.
Comandos úteis:
| Comando | Finalidade |
|---|---|
npm.cmd run dev |
Inicia o Vite em desenvolvimento. |
npm.cmd run dev:full |
Inicia frontend e API local quando disponível. |
npm.cmd run dev:mobile |
Expõe o Vite para testes na rede local. |
npm.cmd run build |
Gera e valida o bundle de produção. |
npm.cmd run lint |
Executa o ESLint. |
src/
├── components/ componentes compartilhados
├── contexts/ loading, toast e estados globais
├── layouts/ layout autenticado e MenuBar
├── pages/ telas organizadas por domínio
├── theme/ tokens, temas e modos de exibição
├── utils/ API, sessão, Socket.IO, permissões e perfil
└── App.jsx rotas públicas e protegidas
Esta seção é a referência de implementação para telas novas. Antes de criar um componente visual do zero, confira se um dos componentes abaixo já resolve o caso. Isso mantém cabeçalhos, filtros, tabelas, avatares e temas consistentes em todo o TM Hub.
- Importe os componentes por seu caminho em
src/components; não copie sua implementação para uma página. - Use os tokens de tema (
var(--surface-1),var(--text-primary),var(--border),var(--primary)) em CSS novo. Não fixe cores de modo claro ou escuro na página. - Datas exibidas ao usuário devem usar
pt-BR. Nos componentes PrimeReact com data, uselocale="pt-BR"quando a propriedade estiver disponível. - Toda rota autenticada deve respeitar a permissão e o escopo global de filial; a API continua sendo a autoridade para validar ambos.
- Para atualizações em tempo real, assine somente o canal do domínio da tela. Um evento de Reposições não deve recarregar Produtos, por exemplo.
Use em toda página interna para manter a hierarquia seção → título → descrição. As ações ficam no mesmo cabeçalho, normalmente Atualizar, Filtros
e Criar.
import { Button } from "primereact/button";
import { PageHeader } from "../../components/PageHeader";
<PageHeader
section="Estoque"
title="Produtos"
description="Gerencie produtos, categorias e níveis mínimos."
actions={
<>
<Button label="Atualizar" icon="pi pi-refresh" outlined onClick={load} />
<Button label="Novo produto" icon="pi pi-plus" onClick={() => setOpen(true)} />
</>
}
/>Propriedades: section, title, description, actions e className.
Use DashCard para indicadores numéricos e DashboardPanel para agrupar
gráficos, tabelas ou listas. Eles recebem as classes globais do TM Hub e se
adaptam aos temas automaticamente.
import { DashCard } from "../../components/DashCard";
import { DashboardPanel } from "../../components/DashboardPanel";
<div className="grid">
<DashCard
title="Em atraso"
value={summary.atrasadas}
detail="requerem tratativa"
icon="pi pi-clock"
tone="danger"
/>
</div>
<DashboardPanel className="my-dashboard-panel">
<header><h2>Movimentações recentes</h2></header>
{/* gráfico, lista ou tabela */}
</DashboardPanel>DashCard aceita title, value, detail, icon, tone, accentColor,
cont (badge complementar), className e style. Para dashboards, também é
possível importar DashboardMetricCard, que é o mesmo card com a classe
tm-dashboard-card aplicada.
É a tabela responsiva padrão. Em desktop ela usa DataTable; em mobile cada célula preserva o rótulo da coluna. Prefira-a às tabelas montadas diretamente em telas administrativas comuns.
import { Table } from "../../components/tables/Table";
const columns = [
{ field: "nome", header: "Produto", sortable: true },
{ field: "quantidade", header: "Estoque", sortable: true },
{
header: "Situação",
mobileHeader: "Situação",
body: (row) => row.ativo ? "Ativo" : "Inativo",
},
];
<Table
data={products}
columns={columns}
loading={loading}
search
rows={10}
rowsPerPageOptions={[10, 25, 50]}
/>Cada coluna aceita field, header, mobileHeader, sortable, body,
style e class. O body recebe a linha e deve retornar o conteúdo React.
Use mode="scroll" apenas quando o painel realmente exigir rolagem vertical;
o padrão é paginação.
É o estado visual padrão para carregamento e ausência de dados. Use-o em toda
nova tela para evitar áreas vazias, mensagens soltas ou skeletons recriados.
Ele respeita os tokens do tema ativo e possui as variantes content, chart,
table, card e dashboard.
import { Placeholder } from "../../components/Placeholder";
if (loading && !data) {
return <Placeholder loading variant="dashboard" />;
}
return metrics.length ? (
<Chart type="bar" data={chartData} options={chartOptions} />
) : (
<Placeholder
variant="chart"
icon="pi-chart-bar"
title="Sem movimentações no período"
description="Altere os filtros ou aguarde novos registros."
/>
);Table já usa esse componente por padrão quando não há resultados. Para
personalizar, passe emptyTitle, emptyDescription, emptyIcon e, quando
necessário, emptyAction. DashboardPanel também aceita loading, empty,
placeholderVariant, emptyTitle, emptyDescription, emptyIcon e
emptyAction.
É o seletor obrigatório quando a tela precisa buscar colaboradores. Ele faz
busca remota, debounce, virtualização e preserva a opção selecionada mesmo
fora da primeira página de resultados. Não carregue toda a base de
colaboradores em um Dropdown local.
import { CollaboratorDropdown } from "../../components/CollaboratorDropdown";
<CollaboratorDropdown
value={form.colaborador_id}
selectedOption={selectedCollaborator}
queryParams={{ ativos: true }}
onChange={(id, collaborator) => {
setForm((current) => ({ ...current, colaborador_id: id }));
setSelectedCollaborator(collaborator);
}}
placeholder="Busque por nome ou matrícula"
/>Principais propriedades: value, onChange(id, option), selectedOption,
queryParams, limit, debounce, minSearch, placeholder, disabled,
showClear e onError. O retorno completo no segundo argumento permite
preencher cargo, matrícula, contrato e local sem nova consulta.
Use para seletores remotos genéricos — contratos, motivos, categorias e demais listas menores — quando não houver um componente específico para o domínio.
import { DropdownWS } from "../../components/DropdownWithSearch";
<DropdownWS
uri="/estoque/categorias"
value={form.categoria_id}
onChange={(categoria_id) => setForm((current) => ({ ...current, categoria_id }))}
placeholder="Selecione a categoria"
minSearch={0}
/>Ele aceita uri, uriParams, value, onChange, optionLabel,
optionValue, optionsValuesForDict, staticOptions, limit, fetchAll,
minSearch e debounce.
Use UserAvatar sempre que um usuário do sistema aparecer: histórico,
comentário, responsável, cabeçalho ou atendimento. O componente prioriza a
foto recebida no payload e, quando necessário, resolve a foto pelo userId.
Sem foto, usa iniciais como fallback. A imagem é renderizada com cover.
import { UserAvatar } from "../../components/UserAvatar";
<UserAvatar
userId={entry.alterado_por_usuario_id}
nome={entry.alterado_por}
foto_perfil={entry.alterado_por_foto}
style={{ width: "2rem", height: "2rem" }}
/>Para membros de Projeto/Kanban, use o adaptador ProjectMemberAvatar:
import ProjectMemberAvatar from "../../components/ProjectMemberAvatar";
<ProjectMemberAvatar member={member} style={{ width: "2rem", height: "2rem" }} />Quando criar ou alterar um endpoint que devolve usuários, inclua id, nome
e foto_perfil no payload sempre que a tela precisar mostrar o avatar. Nunca
use o ID de supervisor como se fosse ID de usuário: são entidades distintas.
Use a marca por este componente em vez de inserir SVG/PNG manualmente. A marca segue o tema ativo e preserva acessibilidade.
import { ThemeLogo } from "../../components/ThemeLogo";
<ThemeLogo variant="inverse" className="my-page-logo" />Use variant="inverse" somente em superfícies escuras; o padrão é
variant="default".
Protege uma rota ou bloco de interface por tela e ação. É uma camada de UX; a mesma autorização precisa continuar validada no backend.
import { PermissionGate } from "../../components/PermissionGate";
<PermissionGate screen="produtos" action="create">
<NewProductButton />
</PermissionGate>
<PermissionGate screen="tm_ops" adminOnly>
<SchedulerManagement />
</PermissionGate>Propriedades: screen, action (view, create ou update), adminOnly
e children. Se não houver acesso, o componente redireciona para /init.
Os diálogos de domínio já existentes devem ser reutilizados em vez de
duplicados: CardDetailDialog, NewProjectDialog, MembersDialog,
RoutineDialog e RoutineLinksDialog. Eles concentram as regras específicas
de cards, membros, rotinas e vínculos.
No Scheduler, TaskExecutionMetrics recebe task e now para exibir duração,
pausas e distância. TaskGeolocationMap recebe geolocations e desenha o
percurso. Ambos devem ser usados somente na visualização da tarefa, pois
dependem dos dados reais já registrados pela execução.
- Crie a rota atrás de
PermissionGatee valide a mesma permissão na API. - Comece com
PageHeadere coloque Atualizar/Filtros/Criar emactions. - Reutilize
DashCard,DashboardPaneleTableconforme a necessidade. - Use selects remotos (
CollaboratorDropdownouDropdownWS) em vez de carregar listas completas. - Exiba pessoas com
UserAvatare enviefoto_perfildo backend. - Use tokens do tema e valide claro, escuro, Cyberpunk e Pride.
- Adicione o listener WebSocket apenas para o domínio da nova tela e limpe-o
no
useEffect.
Esta documentação descreve o contrato atual dos componentes. Quando uma prop compartilhada for adicionada ou alterada, atualize esta seção no mesmo PR para que ela continue sendo a fonte de consulta do time.
- O token é enviado em
Access-Token; nunca grave tokens ou segredos no repositório. - Ocultar um botão não substitui a validação da API.
- O filtro global de filial é uma regra de negócio e segurança. Novas telas autenticadas devem respeitá-lo.
- Rotas públicas são exceções explícitas e não recebem o contexto autenticado de filial.
Leia FLUXO.md antes de iniciar uma atividade. Colaboradores
trabalham em branches e abrem PR para dev; apenas o responsável do projeto
promove dev para main e produção.
As regras de negócio, modelos e API estão em api_tmhub.
Projeto proprietário de uso interno. Distribuição, cópia ou modificação externa dependem de autorização.