diff --git a/README.md b/README.md index c929d31..96b944f 100644 --- a/README.md +++ b/README.md @@ -1,59 +1,57 @@ # Sistema Arte -Plugin WordPress para gerenciamento de tarefas integrado à API Vikunja. +Plugin WordPress para gerenciamento de demandas de arte. ## 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 -- Formulário customizado para solicitação de artes, com campos obrigatórios: - - Título da arte - - Nome completo do solicitante - - Secretaria - - Telefone/WhatsApp - - Detalhes da solicitação - - Data de entrega (padrão: hoje + 7 dias às 17h) - - Prioridade -- Integração direta com a API Vikunja para criação e listagem de tarefas -- Exibição das demandas pendentes em tabela -- Validação de campos obrigatórios e feedback de sucesso/erro -- Interface moderna utilizando Tailwind CSS +- **Formulário de Solicitação:** Um formulário customizado para solicitação de artes, com campos para: + - Título, nome do solicitante, secretaria, contato. + - Detalhes da solicitação e anexo de arquivos. + - Data de entrega e nível de prioridade. +- **Gerenciamento no WordPress:** + - As demandas são salvas como um tipo de post personalizado ("Demandas de Arte"). + - Utiliza uma taxonomia customizada ("Status") para controlar o fluxo. +- **Quadro Kanban:** + - Um painel de administração visual com as colunas: `Demanda`, `Fazer`, `Fazendo` e `Feito`. + - Funcionalidade de arrastar e soltar (drag-and-drop) para mover as demandas entre as colunas e atualizar seu status. +- **IDs Sequenciais:** + - 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 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. -3. Configure as variáveis de integração com a API Vikunja em `includes/config.php`: - - `$apiBase`: URL base da API - - `$token`: Token de acesso - - `$projectId`: ID do projeto no Vikunja +3. Após a ativação, o menu "Demandas de Arte" aparecerá no painel de administração. ## 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. ## Estrutura dos Arquivos - `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/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 - [Tailwind CSS](https://tailwindcss.com/) (via CDN) - 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 - Utiliza `wp_nonce_field` para proteção contra CSRF - Sanitização e validação de todos os campos do formulário - 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 Marco Antonio Vivas --- -Plugin desenvolvido para integração com o sistema Vikunja, facilitando a gestão de demandas de artes no WordPress. \ No newline at end of file +Plugin desenvolvido para facilitar a gestão de demandas de artes diretamente no WordPress. \ No newline at end of file diff --git a/includes/assets/kanban-board.js b/includes/assets/kanban-board.js new file mode 100644 index 0000000..d1c8582 --- /dev/null +++ b/includes/assets/kanban-board.js @@ -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(); +}); \ No newline at end of file diff --git a/includes/assets/kanban-style.css b/includes/assets/kanban-style.css new file mode 100644 index 0000000..3c07c6d --- /dev/null +++ b/includes/assets/kanban-style.css @@ -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; +} \ No newline at end of file diff --git a/includes/assets/script.js b/includes/assets/script.js index c90762c..0205a14 100644 --- a/includes/assets/script.js +++ b/includes/assets/script.js @@ -1,53 +1,20 @@ jQuery(document).ready(function($) { var $form = $('#sistema-arte-form'); if ($form.length) { - // Definir data padrão (hoje + 7 dias) no carregamento - var today = new Date(); - today.setDate(today.getDate() + 7); - today.setHours(17, 0, 0, 0); // Define para 17:00 - - // Formata para o formato esperado pelo input datetime-local (YYYY-MM-DDTHH:MM) - var formattedDate = today.toISOString().slice(0, 16); - - // Define o valor do campo se ele estiver vazio - if (!$('#due_date').val()) { - $('#due_date').val(formattedDate); - } - $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 = - "
" + - "

SOLICITAÇÃO

" + - "" + - "

DETALHES

" + - "

" + additionalInfo.replace(//g, '>') + "

" + - "
"; - - // 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 - } - + // Adiciona máscara ao campo de telefone + var phoneInput = $('#phone'); + if (phoneInput.length && typeof phoneInput.mask === 'function') { + var maskBehavior = function (val) { + return val.replace(/\D/g, '').length === 11 ? '(00) 00000-0000' : '(00) 0000-00009'; + }, + options = {onKeyPress: function(val, e, field, options) { + field.mask(maskBehavior.apply({}, arguments), options); + } + }; + phoneInput.mask(maskBehavior, options); + } + + $form.on('submit', function() { return true; }); } else { diff --git a/includes/templates/form.php b/includes/templates/form.php index 489e176..e8d092a 100644 --- a/includes/templates/form.php +++ b/includes/templates/form.php @@ -1,5 +1,25 @@ '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 $default_due_date = ''; try { @@ -24,7 +44,7 @@ function sistema_arte_form_template($message) { -
+ @@ -51,24 +71,11 @@ function sistema_arte_form_template($message) { @@ -82,8 +89,7 @@ function sistema_arte_form_template($message) { placeholder="(99) 99999-9999"> - - +
@@ -93,6 +99,18 @@ function sistema_arte_form_template($message) { required>
+ +
+ +
+ + +
+
+
@@ -108,10 +126,11 @@ function sistema_arte_form_template($message) {
diff --git a/includes/templates/tasks.php b/includes/templates/tasks.php index 8197970..de3d900 100644 --- a/includes/templates/tasks.php +++ b/includes/templates/tasks.php @@ -16,15 +16,25 @@ function sistema_arte_tasks_template($tasks) { - + + 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' + ]; + ?> - - + ID) : $post->ID); ?> + post_title); ?> format('d/m/Y H:i')); } catch (Exception $e) { echo '-'; @@ -35,11 +45,11 @@ function sistema_arte_tasks_template($tasks) { ?> - + - + "> + - diff --git a/sistema-arte.php b/sistema-arte.php index d888cdb..5ce708c 100644 --- a/sistema-arte.php +++ b/sistema-arte.php @@ -17,8 +17,8 @@ define('SISTEMA_ARTE_PATH', plugin_dir_path(__FILE__)); // Verify and include necessary files $required_files = [ - SISTEMA_ARTE_PATH . 'includes/config.php', - SISTEMA_ARTE_PATH . 'includes/api.php', + //SISTEMA_ARTE_PATH . 'includes/config.php', // Não é mais necessário + //SISTEMA_ARTE_PATH . 'includes/api.php', // Não é mais necessário SISTEMA_ARTE_PATH . 'includes/templates/form.php', SISTEMA_ARTE_PATH . 'includes/templates/tasks.php' ]; @@ -35,10 +35,13 @@ foreach ($required_files as $file) { function sistema_arte_enqueue_assets() { // 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'); - + + // 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 wp_enqueue_script('sistema-arte-script', plugins_url('/includes/assets/script.js', __FILE__), ['jquery'], '1.0.1', true); - + // Localize script for form validation wp_localize_script('sistema-arte-script', 'sistemaArte', [ 'ajax_url' => admin_url('admin-ajax.php'), @@ -47,13 +50,116 @@ function 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 function sistema_arte_shortcode($atts) { // Start output buffering ob_start(); - // Load config variables - global $apiBase, $token, $projectId; + // Não precisamos mais das variáveis de configuração da API + // global $apiBase, $token, $projectId; // Process form submission $message = ''; @@ -65,7 +171,7 @@ function sistema_arte_shortcode($atts) { $additional_info = isset($_POST['additional_info']) ? sanitize_textarea_field(trim($_POST['additional_info'])) : ''; $due_date = isset($_POST['due_date']) ? sanitize_text_field(trim($_POST['due_date'])) : ''; $priority = isset($_POST['priority']) ? absint($_POST['priority']) : null; - + // Validações $errors = []; if (empty($title)) { @@ -91,8 +197,8 @@ function sistema_arte_shortcode($atts) { $errors[] = 'Data de vencimento inválida.'; } } - if ($priority !== null && ($priority < 1 || $priority > 5)) { - $errors[] = 'Prioridade deve ser entre 1 e 5.'; + if ($priority !== null && ($priority < 1 || $priority > 4)) { // Ajustado para 4 opções + $errors[] = 'Prioridade inválida.'; } if (empty($errors)) { @@ -108,38 +214,88 @@ function sistema_arte_shortcode($atts) { "

" . esc_html($additional_info) . "

" . ""; - $task = [ - 'title' => $title, - 'project_id' => $projectId, - 'description' => $description, + $post_data = [ + 'post_title' => $title, + 'post_content' => $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); - if ($result['success']) { - $message = "Tarefa criada com sucesso! ID: {$result['data']['id']}"; - } else { - $message = "Erro ao criar tarefa: {$result['error']} (HTTP {$result['http_code']})"; - if ($result['http_code'] == 403) { - $message .= "
Permissão negada. Verifique se o usuário do token tem acesso de escrita no projeto."; - } elseif ($result['http_code'] == 400) { - $message .= "
Dados inválidos. Verifique o formato dos campos da tarefa."; - } elseif ($result['http_code'] == 404) { - $message .= "
Projeto não encontrado. Confirme se o project_id é válido."; + // Insere o post no banco de dados + $post_id = wp_insert_post($post_data); + + if (!is_wp_error($post_id)) { + // Pega o próximo número do nosso contador sequencial + $next_id = (int) get_option('sistema_arte_demand_counter', 1); + update_post_meta($post_id, '_demanda_id_sequencial', $next_id); + // Incrementa e salva o contador para a próxima demanda + update_option('sistema_arte_demand_counter', $next_id + 1); + + // 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:'; + } + } + + 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 { $message = 'Erros no formulário:'; } } - // List tasks - $tasks = listTasks($apiBase, $token, $projectId); + // Listar demandas do CPT 'arte_demanda' + // 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 ?> @@ -157,4 +313,253 @@ function sistema_arte_shortcode($atts) { return ob_get_clean(); } 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 '' . esc_html(sistema_arte_format_id($post_id)) . ''; + 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 '

Informações do Solicitante

'; + echo '

Nome: ' . esc_html($full_name) . '

'; + echo '

Secretaria: ' . esc_html($department) . '

'; + echo '

Contato: ' . esc_html($phone) . '

'; + + echo '
'; + echo '

Anexos

'; + if ($attachments) { + echo ''; + } else { + echo '

Nenhum arquivo foi anexado a esta demanda.

'; + } +} + +/** + * 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'], + ]; + ?> +
+

Quadro Kanban de Demandas

+
+ + slug]); ?> +
+
+ name); ?> + +
+
+ slug] as $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'; + ?> +
+ +
+ + + ID, '_full_name', true)); ?> + + + + + +
+ +
+ +
+
+ +
+
+ ['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 '' . esc_html($priority_info['label']) . ''; +} + +/** + * 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'); + ?> \ No newline at end of file