knowledge-base/records/plugin-dev/KB-PLUGIN-043-glpi11-kanban-card-ux-patterns.md
Gemini 63e650ed66 KB-PLUGIN-043: padroes de UX de card kanban (anatomia nativa, own_ticket, is_private, TTR, prioridade, gotchas CSS/JS)
Aprendizados do mindscrum 0.7.0-0.9.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 15:56:37 -03:00

4.3 KiB
Raw Blame History

id title domain tags status severity created_at updated_at applies_to related_records
KB-PLUGIN-043 GLPI 11 — Padrões de UX de card kanban em plugin: anatomia do card nativo, own_ticket, is_private, TTR, cores de prioridade e gotchas de CSS plugin-dev
glpi11
plugin
kanban
card
ux
own-ticket
itilfollowup
time-to-resolve
priority
css
gotcha
mindscrum
active medium 2026-07-13 2026-07-13
GLPI 11.0.8 dev
plugin mindscrum 0.7.00.9.0
KB-PLUGIN-040
KB-PLUGIN-041
KB-PLUGIN-042

GLPI 11 — Padrões de UX de card kanban em plugin (aprendizados mindscrum 0.70.9)

Anatomia do card do Kanban NATIVO (receita para replicar)

Fonte: Project::getKanbanColumns() + Vue Kanban-Card (compilado em public/build/vue/vue-sfc/). O card nativo carrega:

  • title; descrição via RichText::getTextFromHtml($content) (em _metadata);
  • link do pai ("Subtask of X") → getFormURLWithID do pai;
  • contador "x / y tasks complete" a partir de _steps (percent_done === 100);
  • barra de progresso Html::progress(100, $percent) (Tabler .progress);
  • _team (badges de membros), due_date = Html::convDateTime(plan_end_date);
  • _form_link; hooks de plugin PRE/POST_KANBAN_CONTENT e KANBAN_ITEM_METADATA.

Replicar server-side com queries EM LOTE (1 p/ equipes, 1 p/ contagens COUNT/SUM(GROUPBY), 1 p/ pais via join, 1 p/ nomes de usuários) — sem N+1.

Técnicos elegíveis para atribuição (dropdown nativo)

User::getSqlSearchResult(false, 'own_ticket', $entities_id, 0, [], '', 0, $limit) → retorna o iterator pronto. 'own_ticket' é o right que o próprio core usa para o ator "atribuído" de Ticket (exclui contas só-requisitantes). Para equipe de projeto, right 'all' na entidade do projeto.

Followup privado (nota "só técnico")

ITILFollowup nativo: campo is_private; right followup com ITILFollowup::SEEPRIVATE. Visibilidade: privada aparece só para o AUTOR ou quem tem SEEPRIVATE — replicar esse filtro ao listar followups por fora da API padrão.

time_to_resolve (prazo/SLA manual)

  • Ticket NÃO tem data prevista de início nativa. Datas do chamado: date (abertura), time_to_resolve, time_to_own (+ internal_*). Início planejado vive nas tasks (TicketTask.begin/end, ProjectTask.plan_*).
  • time_to_resolve aceita valor manual no add()/update() — herdar plan_end_date da task de projeto ao criar chamado dá visão de estouro "de graça".
  • ⚠️ Gotcha: o core DESCARTA SILENCIOSAMENTE time_to_resolve anterior à data de abertura (update() retorna true e o campo fica null). Em testes, usar abertura retroativa (date no input do add) para simular estouro.
  • Barrinha de "SLA": % = (now - date) / (time_to_resolve - date), capado em 100.

Cores/ícones de prioridade

Cores nativas configuráveis em $CFG_GLPI['priority_1'..'priority_6'] (tons de vermelho claro — são cores de FUNDO). Para UI discreta, preferir ícone Tabler + tooltip (getPriorityName): chevrons (baixas), ti-equal (média), ti-exclamation-circle/ti-alert-triangle/ti-alert-octagon (altas).

Gotchas de CSS/JS em modal custom

  • Keyframe que anima transform SUBSTITUI o transform do elemento durante a animação: se a centralização usa translate(-50%,-50%), o elemento "nasce" deslocado e pula ao final. Solução: centralizar com flex no wrapper (position:fixed; inset:0; display:flex) e deixar o keyframe só com scale.
  • Flatpickr com altInput: estilizar via altInputClass (o input visível é NOVO; regras no input original não pegam) — senão o layout quebra.
  • .catch() de fetch(...).then(render) captura exceções do RENDER também — um erro de JS no template do modal aparece como se fosse falha de rede/endpoint. (Sintoma clássico: "modal funciona pra um itemtype e não pra outro".)
  • Popover/sub-modal empilhado sobre modal custom: z-index acima (1070+) e Esc tratado em pilha (fecha o de cima primeiro).

Espelhamento de descrição (padrão mindscrum)

Spawn task→chamado importa título + content (texto via RichText) + prazo. A seção "Descrição" do modal aponta para a FONTE: projeto → Project.content; chamado → ProjectTask.content da task de origem (fallback: do chamado). Editar grava na fonte via update() — allowlist de itemtype/campo no endpoint.