Vikunja para Wordpress
This commit is contained in:
52
README.md
52
README.md
@@ -1,59 +1,57 @@
|
|||||||
# Sistema Arte
|
# Sistema Arte
|
||||||
|
|
||||||
Plugin WordPress para gerenciamento de tarefas integrado à API Vikunja.
|
Plugin WordPress para gerenciamento de demandas de arte.
|
||||||
|
|
||||||
## Descrição
|
## Descrição
|
||||||
O **Sistema Arte** é um plugin para WordPress que permite a criação e acompanhamento de demandas de artes gráficas, integrando-se diretamente à API Vikunja para gerenciamento de tarefas. O plugin oferece um formulário customizado para solicitação de demandas e exibe uma lista das tarefas pendentes do projeto configurado.
|
O **Sistema Arte** é um plugin para WordPress que cria um sistema autônomo para gerenciamento de demandas de arte dentro do painel de administração. Ele permite que usuários enviem solicitações através de um formulário e que os administradores gerenciem o fluxo de trabalho usando um quadro Kanban.
|
||||||
|
|
||||||
## Funcionalidades
|
## Funcionalidades
|
||||||
- Formulário customizado para solicitação de artes, com campos obrigatórios:
|
- **Formulário de Solicitação:** Um formulário customizado para solicitação de artes, com campos para:
|
||||||
- Título da arte
|
- Título, nome do solicitante, secretaria, contato.
|
||||||
- Nome completo do solicitante
|
- Detalhes da solicitação e anexo de arquivos.
|
||||||
- Secretaria
|
- Data de entrega e nível de prioridade.
|
||||||
- Telefone/WhatsApp
|
- **Gerenciamento no WordPress:**
|
||||||
- Detalhes da solicitação
|
- As demandas são salvas como um tipo de post personalizado ("Demandas de Arte").
|
||||||
- Data de entrega (padrão: hoje + 7 dias às 17h)
|
- Utiliza uma taxonomia customizada ("Status") para controlar o fluxo.
|
||||||
- Prioridade
|
- **Quadro Kanban:**
|
||||||
- Integração direta com a API Vikunja para criação e listagem de tarefas
|
- Um painel de administração visual com as colunas: `Demanda`, `Fazer`, `Fazendo` e `Feito`.
|
||||||
- Exibição das demandas pendentes em tabela
|
- Funcionalidade de arrastar e soltar (drag-and-drop) para mover as demandas entre as colunas e atualizar seu status.
|
||||||
- Validação de campos obrigatórios e feedback de sucesso/erro
|
- **IDs Sequenciais:**
|
||||||
- Interface moderna utilizando Tailwind CSS
|
- Sistema de ID personalizado e sequencial (ex: A001, A002) para fácil identificação das demandas.
|
||||||
|
- **Lista de Demandas Pendentes:**
|
||||||
|
- O shortcode exibe uma tabela com todas as demandas que não estão com o status "Feito".
|
||||||
|
- **Interface Moderna:**
|
||||||
|
- Utiliza Tailwind CSS para o formulário e um design limpo para o quadro Kanban.
|
||||||
|
|
||||||
## Instalação
|
## Instalação
|
||||||
1. Faça upload da pasta `sistema-arte` para o diretório `wp-content/plugins/` do seu WordPress.
|
1. Faça upload da pasta `sistema-arte` para o diretório `wp-content/plugins/` do seu WordPress.
|
||||||
2. Ative o plugin no painel do WordPress.
|
2. Ative o plugin no painel do WordPress.
|
||||||
3. Configure as variáveis de integração com a API Vikunja em `includes/config.php`:
|
3. Após a ativação, o menu "Demandas de Arte" aparecerá no painel de administração.
|
||||||
- `$apiBase`: URL base da API
|
|
||||||
- `$token`: Token de acesso
|
|
||||||
- `$projectId`: ID do projeto no Vikunja
|
|
||||||
|
|
||||||
## Uso
|
## Uso
|
||||||
Adicione o shortcode `[Sistema-Arte]` em qualquer página ou post para exibir o formulário de solicitação e a lista de demandas.
|
Adicione o shortcode `[Sistema-Arte]` em qualquer página ou post para exibir o formulário de solicitação e a lista de demandas.
|
||||||
|
|
||||||
## Estrutura dos Arquivos
|
## Estrutura dos Arquivos
|
||||||
- `sistema-arte.php`: Arquivo principal do plugin
|
- `sistema-arte.php`: Arquivo principal do plugin
|
||||||
- `includes/config.php`: Configurações da API
|
|
||||||
- `includes/api.php`: Funções de integração com a API Vikunja
|
|
||||||
- `includes/templates/form.php`: Template do formulário de solicitação
|
- `includes/templates/form.php`: Template do formulário de solicitação
|
||||||
- `includes/templates/tasks.php`: Template da lista de tarefas
|
- `includes/templates/tasks.php`: Template da lista de tarefas
|
||||||
- `includes/assets/script.js`: Scripts JS para manipulação do formulário
|
- `includes/assets/script.js`: Scripts JS para o formulário (máscara de telefone)
|
||||||
|
- `includes/assets/kanban-board.js`: Scripts JS para a funcionalidade do quadro Kanban
|
||||||
|
- `includes/assets/kanban-style.css`: Estilos CSS para o quadro Kanban
|
||||||
|
|
||||||
## Dependências
|
## Dependências
|
||||||
- [Tailwind CSS](https://tailwindcss.com/) (via CDN)
|
- [Tailwind CSS](https://tailwindcss.com/) (via CDN)
|
||||||
- jQuery (WordPress padrão)
|
- jQuery (WordPress padrão)
|
||||||
|
- jQuery UI Sortable (WordPress padrão, para o Kanban)
|
||||||
|
- jQuery Mask Plugin (via CDN, para o campo de telefone)
|
||||||
|
|
||||||
## Segurança
|
## Segurança
|
||||||
- Utiliza `wp_nonce_field` para proteção contra CSRF
|
- Utiliza `wp_nonce_field` para proteção contra CSRF
|
||||||
- Sanitização e validação de todos os campos do formulário
|
- Sanitização e validação de todos os campos do formulário
|
||||||
- Escapando de todas as saídas para evitar XSS
|
- Escapando de todas as saídas para evitar XSS
|
||||||
|
|
||||||
## Observações
|
|
||||||
- O plugin depende de um projeto e token válidos na API Vikunja.
|
|
||||||
- O usuário do token precisa de permissão de escrita no projeto configurado.
|
|
||||||
- Em caso de erro de permissão, dados inválidos ou projeto não encontrado, mensagens detalhadas são exibidas ao usuário.
|
|
||||||
|
|
||||||
## Autor
|
## Autor
|
||||||
Marco Antonio Vivas
|
Marco Antonio Vivas
|
||||||
|
|
||||||
---
|
---
|
||||||
Plugin desenvolvido para integração com o sistema Vikunja, facilitando a gestão de demandas de artes no WordPress.
|
Plugin desenvolvido para facilitar a gestão de demandas de artes diretamente no WordPress.
|
65
includes/assets/kanban-board.js
Normal file
65
includes/assets/kanban-board.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
jQuery(document).ready(function($) {
|
||||||
|
const board = $('#kanban-board');
|
||||||
|
if (board.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Função para inicializar o 'sortable' (arrastar e soltar)
|
||||||
|
$('.kanban-column-body').sortable({
|
||||||
|
connectWith: ".kanban-column-body", // Permite arrastar entre colunas
|
||||||
|
placeholder: "kanban-card-placeholder", // Estilo do espaço reservado
|
||||||
|
opacity: 0.8, // Deixa o card semitransparente ao arrastar
|
||||||
|
revert: 200, // Animação suave ao soltar
|
||||||
|
start: function(event, ui) {
|
||||||
|
// Garante que o placeholder tenha a mesma altura do card arrastado
|
||||||
|
ui.placeholder.height(ui.item.outerHeight());
|
||||||
|
},
|
||||||
|
// Função chamada quando um card é solto em uma nova coluna
|
||||||
|
receive: function(event, ui) {
|
||||||
|
const postId = ui.item.data('post-id');
|
||||||
|
const newStatus = $(this).data('status-slug');
|
||||||
|
const originalFooterHTML = ui.item.find('.kanban-card-footer').html(); // Salva o estado original
|
||||||
|
|
||||||
|
// --- ATUALIZAÇÃO OTIMISTA ---
|
||||||
|
// 1. Atualiza a UI imediatamente para parecer instantâneo.
|
||||||
|
// Adiciona uma classe para um feedback visual sutil.
|
||||||
|
ui.item.addClass('kanban-card-saving');
|
||||||
|
|
||||||
|
// 2. Envia a atualização para o WordPress em segundo plano.
|
||||||
|
$.ajax({
|
||||||
|
url: kanban_ajax.ajax_url,
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
action: 'update_demand_status', // Ação do WordPress
|
||||||
|
nonce: kanban_ajax.nonce,
|
||||||
|
post_id: postId,
|
||||||
|
new_status: newStatus
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
if (response.success) {
|
||||||
|
// Sucesso! A UI já está correta. Apenas damos um feedback de sucesso.
|
||||||
|
ui.item.addClass('kanban-card-success');
|
||||||
|
setTimeout(function() {
|
||||||
|
ui.item.removeClass('kanban-card-success');
|
||||||
|
}, 1500);
|
||||||
|
} else {
|
||||||
|
// Erro! Desfaz a alteração na UI e avisa o usuário.
|
||||||
|
$(ui.sender).sortable('cancel');
|
||||||
|
ui.item.find('.kanban-card-footer').html(originalFooterHTML); // Restaura o rodapé
|
||||||
|
alert('Erro ao atualizar o status: ' + (response.data.message || 'Tente novamente.'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
// Erro de conexão! Também desfaz a alteração.
|
||||||
|
$(ui.sender).sortable('cancel');
|
||||||
|
ui.item.find('.kanban-card-footer').html(originalFooterHTML); // Restaura o rodapé
|
||||||
|
alert('Erro de comunicação. Tente novamente.');
|
||||||
|
},
|
||||||
|
complete: function() {
|
||||||
|
// Independentemente do resultado, remove a classe de "salvando".
|
||||||
|
ui.item.removeClass('kanban-card-saving');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).disableSelection();
|
||||||
|
});
|
160
includes/assets/kanban-style.css
Normal file
160
includes/assets/kanban-style.css
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
/* Estilos Gerais do Quadro Kanban */
|
||||||
|
.wrap h1 {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
#kanban-board {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Colunas do Kanban */
|
||||||
|
.kanban-column {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 300px;
|
||||||
|
max-width: 320px;
|
||||||
|
background-color: #f0f2f5; /* Um cinza mais suave */
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-column-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-card-count {
|
||||||
|
background-color: #e5e7eb;
|
||||||
|
color: #4b5563;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-column-body {
|
||||||
|
padding: 8px;
|
||||||
|
flex-grow: 1;
|
||||||
|
min-height: 500px;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cards de Demanda */
|
||||||
|
.kanban-card {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
padding: 16px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
cursor: grab;
|
||||||
|
border-left-width: 4px;
|
||||||
|
transition: box-shadow 0.2s ease, transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-card:hover {
|
||||||
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-card:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
background-color: #f9fafb;
|
||||||
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-card-title {
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-card-title a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-card-title a:hover {
|
||||||
|
color: #4f46e5; /* Indigo */
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-card-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6b7280;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-card-meta .meta-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-card-meta .dashicons {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #9ca3af;
|
||||||
|
height: 18px;
|
||||||
|
width: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-card-footer {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanban-priority-badge {
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cores para Prioridades */
|
||||||
|
.priority-high { border-left-color: #ef4444; }
|
||||||
|
.priority-high .kanban-priority-badge { background-color: #ef4444; } /* Vermelho */
|
||||||
|
|
||||||
|
.priority-medium-high { border-left-color: #f97316; }
|
||||||
|
.priority-medium-high .kanban-priority-badge { background-color: #f97316; } /* Laranja */
|
||||||
|
|
||||||
|
.priority-medium { border-left-color: #3b82f6; }
|
||||||
|
.priority-medium .kanban-priority-badge { background-color: #3b82f6; } /* Azul */
|
||||||
|
|
||||||
|
.low, .priority-low { border-left-color: #6b7280; }
|
||||||
|
.low .kanban-priority-badge, .priority-low .kanban-priority-badge { background-color: #6b7280; } /* Cinza */
|
||||||
|
|
||||||
|
/* Placeholder para arrastar e soltar */
|
||||||
|
.kanban-card-placeholder {
|
||||||
|
background-color: #e0e7ff;
|
||||||
|
border: 1px dashed #a5b4fc;
|
||||||
|
height: 80px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Feedback de sucesso */
|
||||||
|
.kanban-card-success {
|
||||||
|
transition: background-color 0.5s ease;
|
||||||
|
background-color: #f0fdf4 !important; /* Verde muito claro */
|
||||||
|
border-color: #bbf7d0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.saving-feedback {
|
||||||
|
font-style: italic;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
@@ -1,53 +1,20 @@
|
|||||||
jQuery(document).ready(function($) {
|
jQuery(document).ready(function($) {
|
||||||
var $form = $('#sistema-arte-form');
|
var $form = $('#sistema-arte-form');
|
||||||
if ($form.length) {
|
if ($form.length) {
|
||||||
// Definir data padrão (hoje + 7 dias) no carregamento
|
// Adiciona máscara ao campo de telefone
|
||||||
var today = new Date();
|
var phoneInput = $('#phone');
|
||||||
today.setDate(today.getDate() + 7);
|
if (phoneInput.length && typeof phoneInput.mask === 'function') {
|
||||||
today.setHours(17, 0, 0, 0); // Define para 17:00
|
var maskBehavior = function (val) {
|
||||||
|
return val.replace(/\D/g, '').length === 11 ? '(00) 00000-0000' : '(00) 0000-00009';
|
||||||
// Formata para o formato esperado pelo input datetime-local (YYYY-MM-DDTHH:MM)
|
},
|
||||||
var formattedDate = today.toISOString().slice(0, 16);
|
options = {onKeyPress: function(val, e, field, options) {
|
||||||
|
field.mask(maskBehavior.apply({}, arguments), options);
|
||||||
// Define o valor do campo se ele estiver vazio
|
}
|
||||||
if (!$('#due_date').val()) {
|
};
|
||||||
$('#due_date').val(formattedDate);
|
phoneInput.mask(maskBehavior, options);
|
||||||
}
|
}
|
||||||
$form.on('submit', function(e) {
|
|
||||||
// Coleta todos os dados dos novos campos
|
|
||||||
var fullName = $('#full_name').val() || '';
|
|
||||||
var department = $('#department').val() || '';
|
|
||||||
var phone = $('#phone').val() || '';
|
|
||||||
var additionalInfo = $('#additional_info').val() || '';
|
|
||||||
|
|
||||||
// Formata a descrição com HTML
|
|
||||||
var formattedDescription =
|
|
||||||
"<div style='font-family: Arial, sans-serif;'>" +
|
|
||||||
"<h3 style='color: #2563eb; border-bottom: 1px solid #ddd; padding-bottom: 5px;'>SOLICITAÇÃO</h3>" +
|
|
||||||
"<ul style='list-style-type: none; padding-left: 0;'>" +
|
|
||||||
"<li><strong>Solicitante:</strong> " + fullName.replace(/</g, '<').replace(/>/g, '>') + "</li>" +
|
|
||||||
"<li><strong>Secretaria:</strong> " + department.replace(/</g, '<').replace(/>/g, '>') + "</li>" +
|
|
||||||
"<li><strong>Contato:</strong> " + phone.replace(/</g, '<').replace(/>/g, '>') + "</li>" +
|
|
||||||
"</ul>" +
|
|
||||||
"<h3 style='color: #2563eb; border-bottom: 1px solid #ddd; padding-bottom: 5px;'>DETALHES</h3>" +
|
|
||||||
"<p style='white-space: pre-line;'>" + additionalInfo.replace(/</g, '<').replace(/>/g, '>') + "</p>" +
|
|
||||||
"</div>";
|
|
||||||
|
|
||||||
// Atribui ao campo de descrição que será enviado ao backend
|
|
||||||
$('#description').val(formattedDescription);
|
|
||||||
|
|
||||||
// Define valores padrão para os campos originais se necessário
|
|
||||||
if (!$('#due_date').val()) {
|
|
||||||
var today = new Date();
|
|
||||||
today.setDate(today.getDate() + 7);
|
|
||||||
today.setHours(17, 0);
|
|
||||||
$('#due_date').val(today.toISOString().slice(0, 16));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$('#priority').val()) {
|
|
||||||
$('#priority').val(3); // Prioridade média por padrão
|
|
||||||
}
|
|
||||||
|
|
||||||
|
$form.on('submit', function() {
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@@ -1,5 +1,25 @@
|
|||||||
<?php
|
<?php
|
||||||
function sistema_arte_form_template($message) {
|
function sistema_arte_form_template($message) {
|
||||||
|
// Centralizar opções para facilitar a manutenção
|
||||||
|
$departments = [
|
||||||
|
'SARH' => 'SARH', 'SEMAP' => 'SEMAP', 'SECOM' => 'SECOM', 'SEMCI' => 'SEMCI',
|
||||||
|
'SEDESO' => 'SEDESO', 'SEDUC' => 'SEDUC', 'SEFIN' => 'SEFIN', 'SEGOV' => 'SEGOV',
|
||||||
|
'SEMMA' => 'SEMMA', 'SEMOSP' => 'SEMOSP', 'SEPLAN' => 'SEPLAN', 'PGM' => 'PGM',
|
||||||
|
'SEMS' => 'SEMS', 'SESP' => 'SESP', 'SELTC' => 'SELTC', 'SEDEC' => 'SEDEC',
|
||||||
|
'SEMOB' => 'SEMOB', 'OUTRAS' => 'OUTRAS'
|
||||||
|
];
|
||||||
|
|
||||||
|
$priorities = [
|
||||||
|
'1' => 'Alta',
|
||||||
|
'2' => 'Média-Alta',
|
||||||
|
'3' => 'Média',
|
||||||
|
'4' => 'Baixa'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Valores atuais para manter no formulário em caso de erro
|
||||||
|
$current_department = isset($_POST['department']) ? wp_unslash($_POST['department']) : '';
|
||||||
|
$current_priority = isset($_POST['priority']) ? wp_unslash($_POST['priority']) : '3'; // Padrão é 'Média'
|
||||||
|
|
||||||
// Calcular data padrão (hoje + 7 dias) no formato YYYY-MM-DDTHH:MM
|
// Calcular data padrão (hoje + 7 dias) no formato YYYY-MM-DDTHH:MM
|
||||||
$default_due_date = '';
|
$default_due_date = '';
|
||||||
try {
|
try {
|
||||||
@@ -24,7 +44,7 @@ function sistema_arte_form_template($message) {
|
|||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<form method="POST" class="space-y-4" id="sistema-arte-form">
|
<form method="POST" class="space-y-4" id="sistema-arte-form" enctype="multipart/form-data">
|
||||||
<?php wp_nonce_field('sistema_arte_nonce', 'sistema_arte_nonce'); ?>
|
<?php wp_nonce_field('sistema_arte_nonce', 'sistema_arte_nonce'); ?>
|
||||||
|
|
||||||
<!-- Campo Título (obrigatório) -->
|
<!-- Campo Título (obrigatório) -->
|
||||||
@@ -51,24 +71,11 @@ function sistema_arte_form_template($message) {
|
|||||||
<select id="department" name="department" required
|
<select id="department" name="department" required
|
||||||
class="mt-1 block w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 shadow-sm">
|
class="mt-1 block w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 shadow-sm">
|
||||||
<option value="">Selecione...</option>
|
<option value="">Selecione...</option>
|
||||||
<option value="SARH" <?php echo (isset($_POST['department']) && $_POST['department'] == 'SARH') ? 'selected' : ''; ?>>SARH</option>
|
<?php foreach ($departments as $value => $label): ?>
|
||||||
<option value="SEMAP" <?php echo (isset($_POST['department']) && $_POST['department'] == 'SEMAP') ? 'selected' : ''; ?>>SEMAP</option>
|
<option value="<?php echo esc_attr($value); ?>" <?php selected($current_department, $value); ?>>
|
||||||
<option value="SECOM" <?php echo (isset($_POST['department']) && $_POST['department'] == 'SECOM') ? 'selected' : ''; ?>>SECOM</option>
|
<?php echo esc_html($label); ?>
|
||||||
<option value="SEMCI" <?php echo (isset($_POST['department']) && $_POST['department'] == 'SEMCI') ? 'selected' : ''; ?>>SEMCI</option>
|
</option>
|
||||||
<option value="SEDESO" <?php echo (isset($_POST['department']) && $_POST['department'] == 'SEDESO') ? 'selected' : ''; ?>>SEDESO</option>
|
<?php endforeach; ?>
|
||||||
<option value="SEDUC" <?php echo (isset($_POST['department']) && $_POST['department'] == 'SEDUC') ? 'selected' : ''; ?>>SEDUC</option>
|
|
||||||
<option value="SEFIN" <?php echo (isset($_POST['department']) && $_POST['department'] == 'SEFIN') ? 'selected' : ''; ?>>SEFIN</option>
|
|
||||||
<option value="SEGOV" <?php echo (isset($_POST['department']) && $_POST['department'] == 'SEGOV') ? 'selected' : ''; ?>>SEGOV</option>
|
|
||||||
<option value="SEMMA" <?php echo (isset($_POST['department']) && $_POST['department'] == 'SEMMA') ? 'selected' : ''; ?>>SEMMA</option>
|
|
||||||
<option value="SEMOSP" <?php echo (isset($_POST['department']) && $_POST['department'] == 'SEMOSP') ? 'selected' : ''; ?>>SEMOSP</option>
|
|
||||||
<option value="SEPLAN" <?php echo (isset($_POST['department']) && $_POST['department'] == 'SEPLAN') ? 'selected' : ''; ?>>SEPLAN</option>
|
|
||||||
<option value="PGM" <?php echo (isset($_POST['department']) && $_POST['department'] == 'PGM') ? 'selected' : ''; ?>>PGM</option>
|
|
||||||
<option value="SEMS" <?php echo (isset($_POST['department']) && $_POST['department'] == 'SEMS') ? 'selected' : ''; ?>>SEMS</option>
|
|
||||||
<option value="SESP" <?php echo (isset($_POST['department']) && $_POST['department'] == 'SESP') ? 'selected' : ''; ?>>SESP</option>
|
|
||||||
<option value="SELTC" <?php echo (isset($_POST['department']) && $_POST['department'] == 'SELTC') ? 'selected' : ''; ?>>SELTC</option>
|
|
||||||
<option value="SEDEC" <?php echo (isset($_POST['department']) && $_POST['department'] == 'SEDEC') ? 'selected' : ''; ?>>SEDEC</option>
|
|
||||||
<option value="SEMOB" <?php echo (isset($_POST['department']) && $_POST['department'] == 'SEMOB') ? 'selected' : ''; ?>>SEMOB</option>
|
|
||||||
<option value="OUTRAS" <?php echo (isset($_POST['department']) && $_POST['department'] == 'OUTRAS') ? 'selected' : ''; ?>>OUTRAS</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -82,8 +89,7 @@ function sistema_arte_form_template($message) {
|
|||||||
placeholder="(99) 99999-9999">
|
placeholder="(99) 99999-9999">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Descrição (campo original, agora oculto) -->
|
<!-- O campo 'description' oculto não é mais necessário -->
|
||||||
<input type="hidden" id="description" name="description">
|
|
||||||
|
|
||||||
<!-- Campo para detalhes adicionais -->
|
<!-- Campo para detalhes adicionais -->
|
||||||
<div>
|
<div>
|
||||||
@@ -93,6 +99,18 @@ function sistema_arte_form_template($message) {
|
|||||||
required><?php echo esc_textarea(isset($_POST['additional_info']) ? wp_unslash($_POST['additional_info']) : ''); ?></textarea>
|
required><?php echo esc_textarea(isset($_POST['additional_info']) ? wp_unslash($_POST['additional_info']) : ''); ?></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Campo de Anexo -->
|
||||||
|
<div class="relative">
|
||||||
|
<label for="attachment" class="block text-sm font-medium text-gray-700 mb-1">Anexar Arquivos (opcional)</label>
|
||||||
|
<div class="mt-1 flex items-center">
|
||||||
|
<label for="attachment" class="w-full cursor-pointer bg-white rounded-md border border-gray-300 p-3 shadow-sm flex items-center justify-between hover:border-indigo-500">
|
||||||
|
<span class="text-gray-500" id="attachment-label">Nenhum arquivo selecionado</span>
|
||||||
|
<span class="px-4 py-1.5 text-sm font-semibold text-indigo-700 bg-indigo-100 rounded-full hover:bg-indigo-200">Escolher arquivo</span>
|
||||||
|
</label>
|
||||||
|
<input type="file" id="attachment" name="attachment" class="sr-only" onchange="document.getElementById('attachment-label').textContent = this.files[0] ? this.files[0].name : 'Nenhum arquivo selecionado';">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- NOVOS CAMPOS VISÍVEIS: Data de Entrega e Prioridade -->
|
<!-- NOVOS CAMPOS VISÍVEIS: Data de Entrega e Prioridade -->
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<!-- Data de Entrega -->
|
<!-- Data de Entrega -->
|
||||||
@@ -108,10 +126,11 @@ function sistema_arte_form_template($message) {
|
|||||||
<label for="priority" class="block text-sm font-medium text-gray-700 mb-1">Prioridade</label>
|
<label for="priority" class="block text-sm font-medium text-gray-700 mb-1">Prioridade</label>
|
||||||
<select id="priority" name="priority"
|
<select id="priority" name="priority"
|
||||||
class="mt-1 block w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 shadow-sm">
|
class="mt-1 block w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 shadow-sm">
|
||||||
<option value="1" <?php echo (isset($_POST['priority']) && $_POST['priority'] == '1') ? 'selected' : ''; ?>>Alta</option>
|
<?php foreach ($priorities as $value => $label): ?>
|
||||||
<option value="2" <?php echo (isset($_POST['priority']) && $_POST['priority'] == '2') ? 'selected' : ''; ?>>Média-Alta</option>
|
<option value="<?php echo esc_attr($value); ?>" <?php selected($current_priority, $value); ?>>
|
||||||
<option value="3" <?php echo (!isset($_POST['priority']) || $_POST['priority'] == '3') ? 'selected' : ''; ?>>Média</option>
|
<?php echo esc_html($label); ?>
|
||||||
<option value="4" <?php echo (isset($_POST['priority']) && $_POST['priority'] == '4') ? 'selected' : ''; ?>>Baixa</option>
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -16,15 +16,25 @@ function sistema_arte_tasks_template($tasks) {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php foreach ($tasks as $task): ?>
|
<?php foreach ($tasks as $post): ?>
|
||||||
|
<?php
|
||||||
|
$due_date = get_post_meta($post->ID, '_due_date', true);
|
||||||
|
$priority = get_post_meta($post->ID, '_priority', true);
|
||||||
|
$priorities_text = [
|
||||||
|
'1' => 'Alta',
|
||||||
|
'2' => 'Média-Alta',
|
||||||
|
'3' => 'Média',
|
||||||
|
'4' => 'Baixa'
|
||||||
|
];
|
||||||
|
?>
|
||||||
<tr class="border-b hover:bg-gray-50 transition-colors duration-150">
|
<tr class="border-b hover:bg-gray-50 transition-colors duration-150">
|
||||||
<td class="p-3 text-gray-600"><?php echo esc_html($task['id']); ?></td>
|
<td class="p-3 text-gray-600 font-semibold"><?php echo esc_html(function_exists('sistema_arte_format_id') ? sistema_arte_format_id($post->ID) : $post->ID); ?></td>
|
||||||
<td class="p-3 font-medium text-gray-800"><?php echo esc_html($task['title']); ?></td>
|
<td class="p-3 font-medium text-gray-800"><?php echo esc_html($post->post_title); ?></td>
|
||||||
<td class="p-3 text-gray-600">
|
<td class="p-3 text-gray-600">
|
||||||
<?php
|
<?php
|
||||||
if ($task['due_date']) {
|
if ($due_date) {
|
||||||
try {
|
try {
|
||||||
$date = new DateTime($task['due_date']);
|
$date = new DateTime($due_date);
|
||||||
echo esc_html($date->format('d/m/Y H:i'));
|
echo esc_html($date->format('d/m/Y H:i'));
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
echo '-';
|
echo '-';
|
||||||
@@ -35,11 +45,11 @@ function sistema_arte_tasks_template($tasks) {
|
|||||||
?>
|
?>
|
||||||
</td>
|
</td>
|
||||||
<td class="p-3">
|
<td class="p-3">
|
||||||
<?php if ($task['priority']): ?>
|
<?php if ($priority): ?>
|
||||||
<span class="px-2 py-1 rounded-full text-xs font-medium
|
<span class="px-2 py-1 rounded-full text-xs font-medium
|
||||||
<?php echo esc_attr($task['priority'] <= 2 ? 'bg-red-100 text-red-800' :
|
<?php echo esc_attr($priority <= 2 ? 'bg-red-100 text-red-800' :
|
||||||
($task['priority'] <= 3 ? 'bg-yellow-100 text-yellow-800' : 'bg-green-100 text-green-800')); ?>">
|
($priority <= 3 ? 'bg-yellow-100 text-yellow-800' : 'bg-green-100 text-green-800')); ?>">
|
||||||
<?php echo esc_html($task['priority']); ?>
|
<?php echo esc_html($priorities_text[$priority] ?? 'N/D'); ?>
|
||||||
</span>
|
</span>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<span class="px-2 py-1 rounded-full text-xs font-medium bg-gray-100 text-gray-800">-</span>
|
<span class="px-2 py-1 rounded-full text-xs font-medium bg-gray-100 text-gray-800">-</span>
|
||||||
|
463
sistema-arte.php
463
sistema-arte.php
@@ -17,8 +17,8 @@ define('SISTEMA_ARTE_PATH', plugin_dir_path(__FILE__));
|
|||||||
|
|
||||||
// Verify and include necessary files
|
// Verify and include necessary files
|
||||||
$required_files = [
|
$required_files = [
|
||||||
SISTEMA_ARTE_PATH . 'includes/config.php',
|
//SISTEMA_ARTE_PATH . 'includes/config.php', // Não é mais necessário
|
||||||
SISTEMA_ARTE_PATH . 'includes/api.php',
|
//SISTEMA_ARTE_PATH . 'includes/api.php', // Não é mais necessário
|
||||||
SISTEMA_ARTE_PATH . 'includes/templates/form.php',
|
SISTEMA_ARTE_PATH . 'includes/templates/form.php',
|
||||||
SISTEMA_ARTE_PATH . 'includes/templates/tasks.php'
|
SISTEMA_ARTE_PATH . 'includes/templates/tasks.php'
|
||||||
];
|
];
|
||||||
@@ -36,6 +36,9 @@ function sistema_arte_enqueue_assets() {
|
|||||||
// Enqueue Tailwind CSS via CDN
|
// Enqueue Tailwind CSS via CDN
|
||||||
wp_enqueue_style('sistema-arte-tailwind', 'https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css', [], '2.2.19');
|
wp_enqueue_style('sistema-arte-tailwind', 'https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css', [], '2.2.19');
|
||||||
|
|
||||||
|
// Enqueue jQuery Mask Plugin via CDN para o formulário
|
||||||
|
wp_enqueue_script('jquery-mask', 'https://cdnjs.cloudflare.com/ajax/libs/jquery.mask/1.14.16/jquery.mask.min.js', ['jquery'], '1.14.16', true);
|
||||||
|
|
||||||
// Enqueue custom script
|
// Enqueue custom script
|
||||||
wp_enqueue_script('sistema-arte-script', plugins_url('/includes/assets/script.js', __FILE__), ['jquery'], '1.0.1', true);
|
wp_enqueue_script('sistema-arte-script', plugins_url('/includes/assets/script.js', __FILE__), ['jquery'], '1.0.1', true);
|
||||||
|
|
||||||
@@ -47,13 +50,116 @@ function sistema_arte_enqueue_assets() {
|
|||||||
}
|
}
|
||||||
add_action('wp_enqueue_scripts', 'sistema_arte_enqueue_assets');
|
add_action('wp_enqueue_scripts', 'sistema_arte_enqueue_assets');
|
||||||
|
|
||||||
|
function sistema_arte_enqueue_admin_assets($hook) {
|
||||||
|
// Enqueue Kanban assets only on our specific admin page
|
||||||
|
if ($hook === 'arte_demanda_page_sistema-arte-kanban') {
|
||||||
|
wp_enqueue_style('sistema-arte-kanban-style', plugins_url('/includes/assets/kanban-style.css', __FILE__), [], '1.0');
|
||||||
|
wp_enqueue_script('sistema-arte-kanban-script', plugins_url('/includes/assets/kanban-board.js', __FILE__), ['jquery', 'jquery-ui-sortable'], '1.1.0', true);
|
||||||
|
// Localize script for Kanban AJAX - MOVIDO PARA CÁ
|
||||||
|
wp_localize_script('sistema-arte-kanban-script', 'kanban_ajax', [
|
||||||
|
'ajax_url' => admin_url('admin-ajax.php'),
|
||||||
|
'nonce' => wp_create_nonce('kanban_update_nonce')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_action('admin_enqueue_scripts', 'sistema_arte_enqueue_admin_assets');
|
||||||
|
|
||||||
|
// Registrar o Custom Post Type para as demandas
|
||||||
|
function sistema_arte_register_post_type() {
|
||||||
|
$labels = [
|
||||||
|
'name' => _x('Demandas de Arte', 'Post Type General Name', 'sistema-arte'),
|
||||||
|
'singular_name' => _x('Demanda de Arte', 'Post Type Singular Name', 'sistema-arte'),
|
||||||
|
'menu_name' => __('Demandas de Arte', 'sistema-arte'),
|
||||||
|
'all_items' => __('Todas as Demandas', 'sistema-arte'),
|
||||||
|
'add_new_item' => __('Adicionar Nova Demanda', 'sistema-arte'),
|
||||||
|
'add_new' => __('Adicionar Nova', 'sistema-arte'),
|
||||||
|
];
|
||||||
|
$args = [
|
||||||
|
'label' => __('Demanda de Arte', 'sistema-arte'),
|
||||||
|
'description' => __('Demandas de arte para o sistema', 'sistema-arte'),
|
||||||
|
'labels' => $labels,
|
||||||
|
'supports' => ['title', 'editor', 'author', 'custom-fields'],
|
||||||
|
'hierarchical' => false,
|
||||||
|
'public' => true,
|
||||||
|
'show_ui' => true,
|
||||||
|
'show_in_menu' => true,
|
||||||
|
'menu_position' => 5,
|
||||||
|
'menu_icon' => 'dashicons-art',
|
||||||
|
'show_in_admin_bar' => true,
|
||||||
|
'show_in_nav_menus' => true,
|
||||||
|
'can_export' => true,
|
||||||
|
'has_archive' => true,
|
||||||
|
'exclude_from_search' => false,
|
||||||
|
'publicly_queryable' => true,
|
||||||
|
'capability_type' => 'post',
|
||||||
|
];
|
||||||
|
register_post_type('arte_demanda', $args);
|
||||||
|
}
|
||||||
|
add_action('init', 'sistema_arte_register_post_type');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cria a taxonomia 'Status' para as demandas.
|
||||||
|
*/
|
||||||
|
function sistema_arte_register_status_taxonomy() {
|
||||||
|
$labels = [
|
||||||
|
'name' => _x('Status', 'taxonomy general name', 'sistema-arte'),
|
||||||
|
'singular_name' => _x('Status', 'taxonomy singular name', 'sistema-arte'),
|
||||||
|
'menu_name' => __('Status', 'sistema-arte'),
|
||||||
|
];
|
||||||
|
$args = [
|
||||||
|
'hierarchical' => false,
|
||||||
|
'labels' => $labels,
|
||||||
|
'show_ui' => true,
|
||||||
|
'show_admin_column' => true,
|
||||||
|
'query_var' => true,
|
||||||
|
'rewrite' => ['slug' => 'demanda-status'],
|
||||||
|
];
|
||||||
|
register_taxonomy('demanda_status', ['arte_demanda'], $args);
|
||||||
|
|
||||||
|
// Garante que os status padrão existam
|
||||||
|
$default_statuses = ['Demanda', 'Fazer', 'Fazendo', 'Feito'];
|
||||||
|
foreach ($default_statuses as $status) {
|
||||||
|
if (!term_exists($status, 'demanda_status')) {
|
||||||
|
wp_insert_term($status, 'demanda_status');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_action('init', 'sistema_arte_register_status_taxonomy');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adiciona a página do Kanban ao menu de administração.
|
||||||
|
*/
|
||||||
|
function sistema_arte_add_kanban_page() {
|
||||||
|
add_submenu_page(
|
||||||
|
'edit.php?post_type=arte_demanda', // Parent slug
|
||||||
|
__('Kanban Board', 'sistema-arte'), // Page title
|
||||||
|
__('Kanban', 'sistema-arte'), // Menu title
|
||||||
|
'edit_posts', // Capability
|
||||||
|
'sistema-arte-kanban', // Menu slug
|
||||||
|
'sistema_arte_render_kanban_page' // Callback function
|
||||||
|
);
|
||||||
|
}
|
||||||
|
add_action('admin_menu', 'sistema_arte_add_kanban_page');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formata o ID da demanda com o prefixo 'A' e preenchimento com zeros.
|
||||||
|
*/
|
||||||
|
function sistema_arte_format_id($post_id) {
|
||||||
|
// Tenta obter o ID sequencial primeiro. Se não existir, usa o ID do post como fallback.
|
||||||
|
$sequential_id = get_post_meta($post_id, '_demanda_id_sequencial', true);
|
||||||
|
if (empty($sequential_id)) {
|
||||||
|
$sequential_id = $post_id;
|
||||||
|
}
|
||||||
|
return 'A' . str_pad($sequential_id, 3, '0', STR_PAD_LEFT);
|
||||||
|
}
|
||||||
|
|
||||||
// Register shortcode
|
// Register shortcode
|
||||||
function sistema_arte_shortcode($atts) {
|
function sistema_arte_shortcode($atts) {
|
||||||
// Start output buffering
|
// Start output buffering
|
||||||
ob_start();
|
ob_start();
|
||||||
|
|
||||||
// Load config variables
|
// Não precisamos mais das variáveis de configuração da API
|
||||||
global $apiBase, $token, $projectId;
|
// global $apiBase, $token, $projectId;
|
||||||
|
|
||||||
// Process form submission
|
// Process form submission
|
||||||
$message = '';
|
$message = '';
|
||||||
@@ -91,8 +197,8 @@ function sistema_arte_shortcode($atts) {
|
|||||||
$errors[] = 'Data de vencimento inválida.';
|
$errors[] = 'Data de vencimento inválida.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($priority !== null && ($priority < 1 || $priority > 5)) {
|
if ($priority !== null && ($priority < 1 || $priority > 4)) { // Ajustado para 4 opções
|
||||||
$errors[] = 'Prioridade deve ser entre 1 e 5.';
|
$errors[] = 'Prioridade inválida.';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($errors)) {
|
if (empty($errors)) {
|
||||||
@@ -108,38 +214,88 @@ function sistema_arte_shortcode($atts) {
|
|||||||
"<p style='white-space: pre-line;'>" . esc_html($additional_info) . "</p>" .
|
"<p style='white-space: pre-line;'>" . esc_html($additional_info) . "</p>" .
|
||||||
"</div>";
|
"</div>";
|
||||||
|
|
||||||
$task = [
|
$post_data = [
|
||||||
'title' => $title,
|
'post_title' => $title,
|
||||||
'project_id' => $projectId,
|
'post_content' => $description,
|
||||||
'description' => $description,
|
'post_type' => 'arte_demanda',
|
||||||
|
'post_status' => 'publish', // ou 'pending' se precisar de aprovação
|
||||||
];
|
];
|
||||||
if ($due_date) {
|
|
||||||
$task['due_date'] = $due_date;
|
|
||||||
}
|
|
||||||
if ($priority !== null) {
|
|
||||||
$task['priority'] = $priority;
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = addTask($apiBase, $token, $task, $projectId);
|
// Insere o post no banco de dados
|
||||||
if ($result['success']) {
|
$post_id = wp_insert_post($post_data);
|
||||||
$message = "Tarefa criada com sucesso! ID: {$result['data']['id']}";
|
|
||||||
} else {
|
if (!is_wp_error($post_id)) {
|
||||||
$message = "Erro ao criar tarefa: {$result['error']} (HTTP {$result['http_code']})";
|
// Pega o próximo número do nosso contador sequencial
|
||||||
if ($result['http_code'] == 403) {
|
$next_id = (int) get_option('sistema_arte_demand_counter', 1);
|
||||||
$message .= "<br>Permissão negada. Verifique se o usuário do token tem acesso de escrita no projeto.";
|
update_post_meta($post_id, '_demanda_id_sequencial', $next_id);
|
||||||
} elseif ($result['http_code'] == 400) {
|
// Incrementa e salva o contador para a próxima demanda
|
||||||
$message .= "<br>Dados inválidos. Verifique o formato dos campos da tarefa.";
|
update_option('sistema_arte_demand_counter', $next_id + 1);
|
||||||
} elseif ($result['http_code'] == 404) {
|
|
||||||
$message .= "<br>Projeto não encontrado. Confirme se o project_id é válido.";
|
// Salva os campos customizados como meta dados
|
||||||
|
update_post_meta($post_id, '_full_name', $full_name);
|
||||||
|
update_post_meta($post_id, '_department', $department);
|
||||||
|
update_post_meta($post_id, '_phone', $phone);
|
||||||
|
update_post_meta($post_id, '_due_date', $due_date);
|
||||||
|
update_post_meta($post_id, '_priority', $priority);
|
||||||
|
|
||||||
|
// Define o status inicial como "Demanda"
|
||||||
|
$demanda_term = get_term_by('slug', 'demanda', 'demanda_status');
|
||||||
|
if ($demanda_term) {
|
||||||
|
wp_set_object_terms($post_id, $demanda_term->term_id, 'demanda_status');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lida com o upload do anexo
|
||||||
|
if (!empty($_FILES['attachment']['name'])) {
|
||||||
|
if (!function_exists('wp_handle_upload')) {
|
||||||
|
require_once(ABSPATH . 'wp-admin/includes/file.php');
|
||||||
|
}
|
||||||
|
if (!function_exists('media_handle_upload')) {
|
||||||
|
require_once(ABSPATH . 'wp-admin/includes/media.php');
|
||||||
|
require_once(ABSPATH . 'wp-admin/includes/image.php');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'attachment' é o name do nosso input file
|
||||||
|
$attachment_id = media_handle_upload('attachment', $post_id);
|
||||||
|
|
||||||
|
if (is_wp_error($attachment_id)) {
|
||||||
|
$errors[] = "Erro ao fazer upload do anexo: " . $attachment_id->get_error_message();
|
||||||
|
// Reverte a criação do post se o anexo for crucial e falhar
|
||||||
|
wp_delete_post($post_id, true);
|
||||||
|
$message = 'Erros no formulário:<ul><li>' . implode('</li><li>', array_map('esc_html', $errors)) . '</li></ul>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($errors)) {
|
||||||
|
$message = "Demanda enviada com sucesso! ID: " . sistema_arte_format_id($post_id);
|
||||||
|
// Limpar o POST para não repopular o formulário
|
||||||
|
$_POST = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$message = "Erro ao salvar a demanda: " . $post_id->get_error_message();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$message = 'Erros no formulário:<ul><li>' . implode('</li><li>', array_map('esc_html', $errors)) . '</li></ul>';
|
$message = 'Erros no formulário:<ul><li>' . implode('</li><li>', array_map('esc_html', $errors)) . '</li></ul>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// List tasks
|
// Listar demandas do CPT 'arte_demanda'
|
||||||
$tasks = listTasks($apiBase, $token, $projectId);
|
// Apenas as que NÃO estão com status "Feito"
|
||||||
|
$tasks = get_posts([
|
||||||
|
'post_type' => 'arte_demanda',
|
||||||
|
'numberposts' => -1,
|
||||||
|
'post_status' => 'publish',
|
||||||
|
'orderby' => 'date',
|
||||||
|
'order' => 'DESC',
|
||||||
|
'tax_query' => [
|
||||||
|
[
|
||||||
|
'taxonomy' => 'demanda_status',
|
||||||
|
'field' => 'slug',
|
||||||
|
'terms' => 'feito',
|
||||||
|
'operator' => 'NOT IN',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
// Load templates
|
// Load templates
|
||||||
?>
|
?>
|
||||||
@@ -157,4 +313,253 @@ function sistema_arte_shortcode($atts) {
|
|||||||
return ob_get_clean();
|
return ob_get_clean();
|
||||||
}
|
}
|
||||||
add_shortcode('Sistema-Arte', 'sistema_arte_shortcode');
|
add_shortcode('Sistema-Arte', 'sistema_arte_shortcode');
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ===================================================================
|
||||||
|
* MELHORIAS NO PAINEL DE ADMINISTRAÇÃO DO WORDPRESS
|
||||||
|
* ===================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Adiciona colunas personalizadas à lista de posts de "Demandas de Arte".
|
||||||
|
*/
|
||||||
|
function sistema_arte_add_admin_columns($columns) {
|
||||||
|
$new_columns = [];
|
||||||
|
foreach ($columns as $key => $title) {
|
||||||
|
if ($key === 'cb') { $new_columns[$key] = $title; } // Mantém o checkbox
|
||||||
|
$new_columns[$key] = $title;
|
||||||
|
if ($key === 'title') {
|
||||||
|
$new_columns['solicitante'] = __('Solicitante', 'sistema-arte');
|
||||||
|
$new_columns['secretaria'] = __('Secretaria', 'sistema-arte');
|
||||||
|
$new_columns['due_date'] = __('Data de Entrega', 'sistema-arte');
|
||||||
|
$new_columns['priority'] = __('Prioridade', 'sistema-arte');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Adiciona a nova coluna de ID no início
|
||||||
|
$new_columns = array_merge(['demanda_id' => __('ID Demanda', 'sistema-arte')], $new_columns);
|
||||||
|
// Remove colunas que não são tão relevantes para esta tela
|
||||||
|
unset($new_columns['author']);
|
||||||
|
unset($new_columns['date']);
|
||||||
|
return $new_columns;
|
||||||
|
}
|
||||||
|
add_filter('manage_arte_demanda_posts_columns', 'sistema_arte_add_admin_columns');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 2. Exibe o conteúdo das colunas personalizadas.
|
||||||
|
*/
|
||||||
|
function sistema_arte_display_admin_columns($column, $post_id) {
|
||||||
|
switch ($column) {
|
||||||
|
case 'demanda_id':
|
||||||
|
echo '<strong>' . esc_html(sistema_arte_format_id($post_id)) . '</strong>';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'solicitante':
|
||||||
|
echo esc_html(get_post_meta($post_id, '_full_name', true));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'secretaria':
|
||||||
|
echo esc_html(get_post_meta($post_id, '_department', true));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'due_date':
|
||||||
|
$due_date_iso = get_post_meta($post_id, '_due_date', true);
|
||||||
|
if ($due_date_iso) {
|
||||||
|
try {
|
||||||
|
$date = new DateTime($due_date_iso);
|
||||||
|
echo esc_html($date->format('d/m/Y H:i'));
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo '—';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo '—';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'priority':
|
||||||
|
$priority = get_post_meta($post_id, '_priority', true);
|
||||||
|
$priorities_text = ['1' => 'Alta', '2' => 'Média-Alta', '3' => 'Média', '4' => 'Baixa'];
|
||||||
|
echo esc_html($priorities_text[$priority] ?? 'N/D');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_action('manage_arte_demanda_posts_custom_column', 'sistema_arte_display_admin_columns', 10, 2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3. Adiciona um Meta Box na tela de edição da demanda para exibir os detalhes.
|
||||||
|
*/
|
||||||
|
function sistema_arte_add_details_meta_box() {
|
||||||
|
add_meta_box(
|
||||||
|
'sistema_arte_details', // ID do Meta Box
|
||||||
|
'Detalhes da Solicitação', // Título
|
||||||
|
'sistema_arte_display_details_meta_box', // Função de callback para renderizar o conteúdo
|
||||||
|
'arte_demanda', // Post Type
|
||||||
|
'normal', // Contexto (normal, side)
|
||||||
|
'high' // Prioridade (high, core, default, low)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
add_action('add_meta_boxes', 'sistema_arte_add_details_meta_box');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renderiza o conteúdo do Meta Box com os detalhes da demanda.
|
||||||
|
*/
|
||||||
|
function sistema_arte_display_details_meta_box($post) {
|
||||||
|
// Recupera todos os metadados
|
||||||
|
$full_name = get_post_meta($post->ID, '_full_name', true);
|
||||||
|
$department = get_post_meta($post->ID, '_department', true);
|
||||||
|
$phone = get_post_meta($post->ID, '_phone', true);
|
||||||
|
|
||||||
|
// Recupera os anexos associados a este post
|
||||||
|
$attachments = get_attached_media('', $post->ID);
|
||||||
|
|
||||||
|
// Exibe os dados de forma organizada
|
||||||
|
echo '<h4>Informações do Solicitante</h4>';
|
||||||
|
echo '<p><strong>Nome:</strong> ' . esc_html($full_name) . '</p>';
|
||||||
|
echo '<p><strong>Secretaria:</strong> ' . esc_html($department) . '</p>';
|
||||||
|
echo '<p><strong>Contato:</strong> ' . esc_html($phone) . '</p>';
|
||||||
|
|
||||||
|
echo '<hr style="margin: 15px 0;">';
|
||||||
|
echo '<h4>Anexos</h4>';
|
||||||
|
if ($attachments) {
|
||||||
|
echo '<ul>';
|
||||||
|
foreach ($attachments as $attachment) {
|
||||||
|
echo '<li><a href="' . esc_url(wp_get_attachment_url($attachment->ID)) . '" target="_blank">' . esc_html(get_the_title($attachment->ID)) . '</a></li>';
|
||||||
|
}
|
||||||
|
echo '</ul>';
|
||||||
|
} else {
|
||||||
|
echo '<p>Nenhum arquivo foi anexado a esta demanda.</p>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renderiza a página do Kanban Board.
|
||||||
|
*/
|
||||||
|
function sistema_arte_render_kanban_page() {
|
||||||
|
$statuses = get_terms(['taxonomy' => 'demanda_status', 'hide_empty' => false]);
|
||||||
|
$demands_by_status = [];
|
||||||
|
|
||||||
|
foreach ($statuses as $status) {
|
||||||
|
$demands_by_status[$status->slug] = get_posts([
|
||||||
|
'post_type' => 'arte_demanda',
|
||||||
|
'numberposts' => -1,
|
||||||
|
'post_status' => 'publish',
|
||||||
|
'tax_query' => [
|
||||||
|
[
|
||||||
|
'taxonomy' => 'demanda_status',
|
||||||
|
'field' => 'slug',
|
||||||
|
'terms' => $status->slug,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$priorities_map = [
|
||||||
|
'1' => ['label' => 'Alta', 'class' => 'priority-high'],
|
||||||
|
'2' => ['label' => 'Média-Alta', 'class' => 'priority-medium-high'],
|
||||||
|
'3' => ['label' => 'Média', 'class' => 'priority-medium'],
|
||||||
|
'4' => ['label' => 'Baixa', 'class' => 'priority-low'],
|
||||||
|
];
|
||||||
|
?>
|
||||||
|
<div class="wrap">
|
||||||
|
<h1>Quadro Kanban de Demandas</h1>
|
||||||
|
<div id="kanban-board">
|
||||||
|
<?php foreach ($statuses as $status): ?>
|
||||||
|
<?php $card_count = count($demands_by_status[$status->slug]); ?>
|
||||||
|
<div class="kanban-column">
|
||||||
|
<div class="kanban-column-header">
|
||||||
|
<span><?php echo esc_html($status->name); ?></span>
|
||||||
|
<span class="kanban-card-count"><?php echo esc_html($card_count); ?></span>
|
||||||
|
</div>
|
||||||
|
<div class="kanban-column-body" data-status-slug="<?php echo esc_attr($status->slug); ?>">
|
||||||
|
<?php foreach ($demands_by_status[$status->slug] as $post): ?>
|
||||||
|
<?php
|
||||||
|
$priority_key = get_post_meta($post->ID, '_priority', true) ?: '4';
|
||||||
|
$priority_info = $priorities_map[$priority_key] ?? $priorities_map['4'];
|
||||||
|
$due_date_iso = get_post_meta($post->ID, '_due_date', true);
|
||||||
|
$due_date_formatted = $due_date_iso ? (new DateTime($due_date_iso))->format('d/m/Y') : 'Sem prazo';
|
||||||
|
?>
|
||||||
|
<div class="kanban-card <?php echo esc_attr($priority_info['class']); ?>" data-post-id="<?php echo esc_attr($post->ID); ?>">
|
||||||
|
<div class="kanban-card-title">
|
||||||
|
<a href="<?php echo get_edit_post_link($post->ID); ?>" target="_blank">
|
||||||
|
<?php echo esc_html(sistema_arte_format_id($post->ID)); ?> - <?php echo esc_html($post->post_title); ?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="kanban-card-meta">
|
||||||
|
<span class="meta-item">
|
||||||
|
<span class="dashicons dashicons-admin-users"></span>
|
||||||
|
<?php echo esc_html(get_post_meta($post->ID, '_full_name', true)); ?>
|
||||||
|
</span>
|
||||||
|
<span class="meta-item">
|
||||||
|
<span class="dashicons dashicons-calendar-alt"></span>
|
||||||
|
<?php echo esc_html($due_date_formatted); ?>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="kanban-card-footer">
|
||||||
|
<span class="kanban-priority-badge"><?php echo esc_html($priority_info['label']); ?></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Função auxiliar para gerar o HTML do rodapé do card.
|
||||||
|
*/
|
||||||
|
function sistema_arte_get_card_footer_html($post_id) {
|
||||||
|
$priorities_map = [
|
||||||
|
'1' => ['label' => 'Alta', 'class' => 'priority-high'],
|
||||||
|
'2' => ['label' => 'Média-Alta', 'class' => 'priority-medium-high'],
|
||||||
|
'3' => ['label' => 'Média', 'class' => 'priority-medium'],
|
||||||
|
'4' => ['label' => 'Baixa', 'class' => 'priority-low'],
|
||||||
|
];
|
||||||
|
$priority_key = get_post_meta($post_id, '_priority', true) ?: '4';
|
||||||
|
$priority_info = $priorities_map[$priority_key] ?? $priorities_map['4'];
|
||||||
|
return '<span class="kanban-priority-badge">' . esc_html($priority_info['label']) . '</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manipulador AJAX para atualizar o status da demanda.
|
||||||
|
*/
|
||||||
|
function sistema_arte_update_demand_status() {
|
||||||
|
// 1. Segurança: Verificar nonce e permissões
|
||||||
|
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'kanban_update_nonce')) {
|
||||||
|
wp_send_json_error(['message' => 'Falha na verificação de segurança.'], 403);
|
||||||
|
}
|
||||||
|
if (!current_user_can('edit_posts')) {
|
||||||
|
wp_send_json_error(['message' => 'Você não tem permissão para fazer isso.'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Sanitizar dados de entrada
|
||||||
|
$post_id = isset($_POST['post_id']) ? absint($_POST['post_id']) : 0;
|
||||||
|
$new_status_slug = isset($_POST['new_status']) ? sanitize_key($_POST['new_status']) : '';
|
||||||
|
|
||||||
|
if (!$post_id || empty($new_status_slug)) {
|
||||||
|
wp_send_json_error(['message' => 'Dados inválidos.'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Lógica de atualização
|
||||||
|
$term = get_term_by('slug', $new_status_slug, 'demanda_status');
|
||||||
|
if (!$term) {
|
||||||
|
wp_send_json_error(['message' => 'Status não encontrado.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Atualiza o termo (status) do post
|
||||||
|
$result = wp_set_object_terms($post_id, $term->term_id, 'demanda_status');
|
||||||
|
|
||||||
|
if (is_wp_error($result)) {
|
||||||
|
wp_send_json_error(['message' => $result->get_error_message()], 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Enviar resposta de sucesso
|
||||||
|
wp_send_json_success([
|
||||||
|
'message' => 'Status atualizado com sucesso!',
|
||||||
|
'footer_html' => sistema_arte_get_card_footer_html($post_id)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
add_action('wp_ajax_update_demand_status', 'sistema_arte_update_demand_status');
|
||||||
|
|
||||||
?>
|
?>
|
Reference in New Issue
Block a user