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

98 lines
4.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
id: KB-PLUGIN-043
title: "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"
domain: plugin-dev
tags:
- glpi11
- plugin
- kanban
- card
- ux
- own-ticket
- itilfollowup
- time-to-resolve
- priority
- css
- gotcha
- mindscrum
status: active
severity: medium
created_at: 2026-07-13
updated_at: 2026-07-13
applies_to:
- GLPI 11.0.8 dev
- plugin mindscrum 0.7.00.9.0
related_records:
- 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.