From df0bf77ba7af2aaa2d04090853265dec03875e52 Mon Sep 17 00:00:00 2001 From: Marco Antonio Vivas Date: Wed, 17 Sep 2025 01:43:51 -0300 Subject: [PATCH] Initial commit --- .gitattributes | 2 + assets/css/admin.css | 40 +++++ assets/css/frontend.css | 345 ++++++++++++++++++++++++++++++++++++ assets/css/style.css | 152 ++++++++++++++++ assets/js/frontend.js | 11 ++ includes/admin-ui.php | 185 +++++++++++++++++++ includes/assets.php | 25 +++ includes/frontend.php | 99 +++++++++++ includes/notifications.php | 51 ++++++ includes/post-types.php | 107 +++++++++++ referencia.md | 189 ++++++++++++++++++++ simple-ticket-system.php | 49 +++++ templates/form-ticket.php | 49 +++++ templates/single-ticket.php | 66 +++++++ templates/view-ticket.php | 47 +++++ 15 files changed, 1417 insertions(+) create mode 100644 .gitattributes create mode 100644 assets/css/admin.css create mode 100644 assets/css/frontend.css create mode 100644 assets/css/style.css create mode 100644 assets/js/frontend.js create mode 100644 includes/admin-ui.php create mode 100644 includes/assets.php create mode 100644 includes/frontend.php create mode 100644 includes/notifications.php create mode 100644 includes/post-types.php create mode 100644 referencia.md create mode 100644 simple-ticket-system.php create mode 100644 templates/form-ticket.php create mode 100644 templates/single-ticket.php create mode 100644 templates/view-ticket.php diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/assets/css/admin.css b/assets/css/admin.css new file mode 100644 index 0000000..4d7a982 --- /dev/null +++ b/assets/css/admin.css @@ -0,0 +1,40 @@ +/* Estilos para a lista de tickets no painel de administração */ + +/* Ajuste de largura das colunas */ +.post-type-ticket .wp-list-table #ticket_id { + width: 8%; +} + +.post-type-ticket .wp-list-table #status { + width: 12%; +} + +.post-type-ticket .wp-list-table #ticket_type { + width: 15%; +} + +/* Estilo base para as etiquetas de status */ +.sts-admin-status { + display: inline-block; + padding: 4px 12px; + border-radius: 15px; + font-weight: 600; + font-size: 12px; + color: #fff; + text-shadow: none; + white-space: nowrap; +} + +/* Cores das etiquetas de status */ +.sts-admin-status.status-aberto { + background-color: #ffb700; /* Amarelo */ + color: #212529; /* Preto */ +} + +.sts-admin-status.status-em-andamento { + background-color: #0582ca; /* Azul Claro */ +} + +.sts-admin-status.status-resolvido { + background-color: #028c6a; /* Verde Sucesso */ +} \ No newline at end of file diff --git a/assets/css/frontend.css b/assets/css/frontend.css new file mode 100644 index 0000000..4abd807 --- /dev/null +++ b/assets/css/frontend.css @@ -0,0 +1,345 @@ +/* Importando as variáveis de referência */ +:root { + --azul-principal: #006494; + --azul-escuro: #003554; + --azul-claro: #0582ca; + --amarelo: #ffb700; + --cinza-claro: #f8f9fa; + --cinza: #e9ecef; + --preto: #212529; + --branco: #ffffff; + --verde-sucesso: #028c6a; + --vermelho-erro: #a94442; + --sombra: 0 4px 6px rgba(0, 0, 0, 0.1); + --borda-radius: 8px; + --transicao: all 0.3s ease; +} + +/* Estilo do Formulário de Ticket */ +.sts-ticket-form { + background: var(--branco); + padding: 30px; + border-radius: var(--borda-radius); + box-shadow: var(--sombra); + max-width: 700px; + margin: 20px auto; +} + +.sts-ticket-form p { + margin-bottom: 20px; +} + +.sts-ticket-form label { + display: block; + margin-bottom: 8px; + font-weight: 600; + color: var(--azul-escuro); +} + +.sts-ticket-form input[type="text"], +.sts-ticket-form textarea, +.sts-ticket-form select { + width: 100%; + padding: 12px; + border: 1px solid var(--cinza); + border-radius: var(--borda-radius); + transition: var(--transicao); + background-color: var(--cinza-claro); + color: var(--preto); +} + +.sts-ticket-form input[type="text"]:focus, +.sts-ticket-form textarea:focus, +.sts-ticket-form select:focus { + outline: none; + border-color: var(--azul-principal); + box-shadow: 0 0 0 3px rgba(0, 100, 148, 0.15); +} + +.sts-ticket-form small { + display: block; + margin-top: 5px; + color: #777; +} + +/* Botão de Envio */ +.sts-ticket-form input[type="submit"] { + background: var(--azul-principal); + color: var(--branco); + padding: 12px 25px; + border-radius: var(--borda-radius); + font-weight: 600; + text-decoration: none; + transition: var(--transicao); + display: inline-block; + border: none; + cursor: pointer; + font-size: 1rem; +} + +.sts-ticket-form input[type="submit"]:hover { + background: var(--azul-escuro); + box-shadow: var(--sombra); +} + +/* Alertas de Sucesso/Erro */ +.sts-alert { + padding: 15px; + margin-bottom: 20px; + border-radius: var(--borda-radius); + max-width: 700px; + margin: 20px auto; + font-weight: 500; +} + +.sts-alert-success { + background-color: #e6f7ee; + border-left: 5px solid var(--verde-sucesso); + color: var(--verde-sucesso); +} + +.sts-alert-error { + background-color: #f2dede; + border-left: 5px solid var(--vermelho-erro); + color: var(--vermelho-erro); +} + +/* Estilo customizado para o input de arquivo */ +.sts-file-input-wrapper { + position: relative; + overflow: hidden; + display: inline-block; + cursor: pointer; +} + +.sts-file-input-wrapper input[type=file] { + position: absolute; + left: 0; + top: 0; + opacity: 0; + cursor: pointer; +} + +.sts-file-input-button { + background: var(--cinza); + color: var(--preto); + padding: 10px 15px; + border-radius: var(--borda-radius); + border: 1px solid #ccc; + display: inline-block; +} + +.sts-file-name { + margin-left: 10px; + font-style: italic; + color: #555; +} + +/* Estilo da Lista de Tickets (Meus Tickets) */ +.sts-section-title { + color: var(--azul-escuro); + margin-bottom: 25px; + font-size: 1.8rem; + border-bottom: 2px solid var(--cinza); + padding-bottom: 10px; +} + +.sts-ticket-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 25px; +} + +.sts-ticket-card { + background: var(--branco); + border-radius: var(--borda-radius); + box-shadow: var(--sombra); + padding: 20px; + text-decoration: none; + color: var(--preto); + display: flex; + flex-direction: column; + justify-content: space-between; + transition: var(--transicao); + position: relative; + border-left: 5px solid var(--cinza); + animation: fadeIn 0.5s ease-out forwards; +} + +.sts-ticket-card:hover { + transform: translateY(-5px); + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); +} + +.sts-card-ticket-id { + position: absolute; + top: -10px; + left: -10px; + background: var(--azul-escuro); + color: var(--branco); + padding: 5px 10px; + font-size: 0.8rem; + font-weight: bold; + border-radius: var(--borda-radius); +} + +.sts-card-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 15px; +} + +.sts-card-title { + font-size: 1.2rem; + color: var(--azul-escuro); + margin: 0; + font-weight: 600; +} + +.sts-ticket-status { + font-size: 0.8rem; + font-weight: 700; + padding: 4px 10px; + border-radius: 15px; + color: var(--branco); + white-space: nowrap; +} + +.sts-card-body p { + margin: 0 0 10px; + color: #555; +} + +.sts-card-footer { + margin-top: auto; + padding-top: 15px; + border-top: 1px solid var(--cinza); + font-size: 0.85rem; + color: #777; +} + +/* Cores dos Status */ +/* Aberto: Laranja */ +.sts-ticket-card.aberto { border-left-color: var(--amarelo); } +.sts-ticket-status.aberto { background-color: var(--amarelo); color: var(--preto); } + +/* Em Andamento: Azul */ +.sts-ticket-card.em-andamento { border-left-color: var(--azul-claro); } +.sts-ticket-status.em-andamento { background-color: var(--azul-claro); color: var(--branco); } + +/* Resolvido: Verde */ +.sts-ticket-card.resolvido { border-left-color: var(--verde-sucesso); } +.sts-ticket-status.resolvido { background-color: var(--verde-sucesso); color: var(--branco); } + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } +} + +/* Estilos para a página de um único ticket (single-ticket.php) */ +.sts-content-area { + max-width: 900px; + margin: 20px auto; + padding: 20px; +} + +.sts-single-ticket { + background: var(--branco); + padding: 30px; + border-radius: var(--borda-radius); + box-shadow: var(--sombra); +} + +.sts-title-wrapper { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; +} + +.sts-single-ticket-id { + background: var(--cinza); + padding: 5px 15px; + border-radius: 20px; + font-weight: bold; + color: var(--azul-escuro); +} + +.sts-ticket-header .sts-ticket-title { + font-size: 2rem; + color: var(--azul-escuro); + margin-top: 0; + margin-bottom: 15px; +} + +.sts-ticket-meta { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 15px; + padding-bottom: 20px; + margin-bottom: 20px; + border-bottom: 1px solid var(--cinza); +} + +.sts-ticket-meta .sts-ticket-status { + order: -1; /* Coloca o status primeiro */ +} + +.sts-ticket-content, +.sts-ticket-attachments { + margin-bottom: 30px; +} + +.sts-ticket-content h3, +.sts-ticket-attachments h3 { + color: var(--azul-escuro); + border-bottom: 2px solid var(--cinza); + padding-bottom: 5px; + margin-bottom: 15px; +} + +/* Estilos para a seção de respostas (comentários do WordPress) */ +.sts-ticket-responses .comments-title { + font-size: 1.5rem; + color: var(--azul-escuro); +} + +.sts-ticket-responses .comment-list { + list-style: none; + padding: 0; +} + +.sts-ticket-responses .comment-body { + background: var(--cinza-claro); + padding: 20px; + border-radius: var(--borda-radius); + margin-bottom: 20px; + border-left: 4px solid var(--azul-principal); +} + +.sts-ticket-responses .comment-meta { + margin-bottom: 10px; +} + +.sts-ticket-responses .comment-form-comment textarea { + width: 100%; + padding: 12px; + border: 1px solid var(--cinza); + border-radius: var(--borda-radius); +} + +.sts-ticket-responses .form-submit .submit { + background: var(--azul-principal); + color: var(--branco); + padding: 10px 20px; + border-radius: var(--borda-radius); + border: none; + cursor: pointer; + transition: var(--transicao); +} + +.sts-ticket-responses .form-submit .submit:hover { + background: var(--azul-escuro); +} \ No newline at end of file diff --git a/assets/css/style.css b/assets/css/style.css new file mode 100644 index 0000000..98d7ac1 --- /dev/null +++ b/assets/css/style.css @@ -0,0 +1,152 @@ +/* Variáveis CSS Globais */ +:root { + --azul-principal: #006494; + --azul-escuro: #003554; + --azul-claro: #0582ca; + --azul-brilhante: #00a6fb; + --amarelo: #ffb700; + --cinza-claro: #f8f9fa; + --cinza: #e9ecef; + --preto: #212529; + --branco: #ffffff; + --sombra: 0 4px 6px rgba(0, 0, 0, 0.1); + --borda-radius: 8px; + --transicao: all 0.3s ease; +} + +/* Tipografia Base */ +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; + color: var(--preto); + line-height: 1.6; +} + +/* Layout Base */ +.grid-layout { + display: grid; + grid-template-columns: 250px 1fr; + gap: 30px; + padding: 20px; +} + +/* Botões e Links */ +.btn-primario { + background: var(--branco); + color: var(--azul-principal); + padding: 12px 25px; + border-radius: var(--borda-radius); + font-weight: 600; + text-decoration: none; + transition: var(--transicao); + display: inline-block; +} + +.btn-primario:hover { + background: var(--azul-principal); + color: var(--branco); + box-shadow: var(--sombra); +} + +/* Cards */ +.ticket-card { + background: var(--branco); + border-radius: var(--borda-radius); + padding: 25px; + margin-bottom: 20px; + box-shadow: var(--sombra); + transition: var(--transicao); +} + +.ticket-card:hover { + transform: translateY(-5px); + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); +} + +/* Formulários */ +.form-grupo { + margin-bottom: 20px; +} + +.form-grupo label { + display: block; + margin-bottom: 8px; + font-weight: 500; + color: var(--azul-escuro); +} + +.form-grupo input[type="text"], +.form-grupo input[type="email"], +.form-grupo textarea { + width: 100%; + padding: 12px; + border: 1px solid var(--cinza); + border-radius: var(--borda-radius); + transition: var(--transicao); +} + +.form-grupo input[type="text"]:focus, +.form-grupo input[type="email"]:focus, +.form-grupo textarea:focus { + outline: none; + border-color: var(--azul-principal); + box-shadow: 0 0 0 2px rgba(0, 100, 148, 0.1); +} + +/* Status do Ticket */ +.status-badge { + display: inline-block; + padding: 6px 12px; + border-radius: 20px; + font-size: 0.875rem; + font-weight: 600; +} + +.status-aberto { + background-color: var(--azul-claro); + color: var(--branco); +} + +.status-fechado { + background-color: var(--cinza); + color: var(--preto); +} + +.status-andamento { + background-color: var(--amarelo); + color: var(--preto); +} + +/* Animações */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.animate-fade-in { + animation: fadeIn 0.5s ease-out forwards; +} + +/* Media Queries */ +@media (max-width: 900px) { + .grid-layout { + display: block; + } + + .ticket-card { + margin: 15px 0; + } +} + +@media (max-width: 600px) { + .btn-primario { + display: block; + text-align: center; + width: 100%; + } +} diff --git a/assets/js/frontend.js b/assets/js/frontend.js new file mode 100644 index 0000000..d6981ce --- /dev/null +++ b/assets/js/frontend.js @@ -0,0 +1,11 @@ +document.addEventListener('DOMContentLoaded', function() { + const fileInput = document.getElementById('sts_ticket_attachment'); + if (fileInput) { + fileInput.addEventListener('change', function() { + const fileNameSpan = document.getElementById('sts-file-name'); + if (this.files.length > 0) { + fileNameSpan.textContent = this.files[0].name; + } + }); + } +}); \ No newline at end of file diff --git a/includes/admin-ui.php b/includes/admin-ui.php new file mode 100644 index 0000000..f34ce8c --- /dev/null +++ b/includes/admin-ui.php @@ -0,0 +1,185 @@ +ID, 'ticket_status', array('fields' => 'names')); + $current_status = !empty($status) ? $status[0] : 'Aberto'; + + $type = wp_get_object_terms($post->ID, 'ticket_type', array('fields' => 'names')); + $current_type = !empty($type) ? $type[0] : __('Nenhum', 'simple-ticket-system'); + + $author_id = $post->post_author; + $author = get_userdata($author_id); + $attachments = get_attached_media('', $post->ID); + $ticket_id = get_post_meta($post->ID, '_sts_ticket_id', true); + + echo '

' . __('ID do Ticket:', 'simple-ticket-system') . ' ' . esc_html($ticket_id) . '

'; + + echo '

' . __('Criado por:', 'simple-ticket-system') . ' ' . $author->display_name . '

'; + echo '

' . __('Email:', 'simple-ticket-system') . ' ' . $author->user_email . '

'; + echo '

' . __('Data:', 'simple-ticket-system') . ' ' . get_the_date('d/m/Y H:i', $post->ID) . '

'; + + echo ''; + echo ''; + + echo '

' . __('Tipo de Solicitação:', 'simple-ticket-system') . ' ' . esc_html($current_type) . '

'; + + if (!empty($attachments)) { + echo '

' . __('Anexos:', 'simple-ticket-system') . '

'; + echo ''; + } else { + echo '

' . __('Anexos:', 'simple-ticket-system') . ' ' . __('Nenhum anexo.', 'simple-ticket-system') . '

'; + } + +} + +// Callback para respostas do ticket +function sts_ticket_responses_callback($post) { + $responses = get_comments(array( + 'post_id' => $post->ID, + 'order' => 'ASC' + )); + + echo '
'; + + if ($responses) { + foreach ($responses as $response) { + echo '
'; + echo '

' . $response->comment_author . ' ' . date('d/m/Y H:i', strtotime($response->comment_date)) . '

'; + echo '
' . wpautop($response->comment_content) . '
'; + echo '

'; + } + } else { + echo '

' . __('Nenhuma resposta ainda.', 'simple-ticket-system') . '

'; + } + + echo '
'; + + // Formulário para nova resposta + echo '

' . __('Adicionar Resposta', 'simple-ticket-system') . '

'; + echo ''; + echo '

' . __('Digite sua resposta acima e atualize o ticket.', 'simple-ticket-system') . '

'; +} + +// Salvar dados do ticket +function sts_save_ticket_data($post_id) { + if (!isset($_POST['sts_ticket_nonce']) || !wp_verify_nonce($_POST['sts_ticket_nonce'], 'sts_save_ticket_data')) { + return; + } + + if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { + return; + } + + if (!current_user_can('edit_post', $post_id)) { + return; + } + + // Salvar status + if (isset($_POST['ticket_status'])) { + $status_name = sanitize_text_field($_POST['ticket_status']); + $term = get_term_by('name', $status_name, 'ticket_status'); + if ($term && !is_wp_error($term)) { + wp_set_object_terms($post_id, $term->term_id, 'ticket_status'); + } else { + wp_set_object_terms($post_id, $status_name, 'ticket_status'); + } + } + + // Salvar resposta se houver + if (!empty($_POST['sts_response_content'])) { + $user = wp_get_current_user(); + + $comment_data = array( + 'comment_post_ID' => $post_id, + 'comment_author' => $user->display_name, + 'comment_author_email' => $user->user_email, + 'comment_content' => wp_kses_post($_POST['sts_response_content']), + 'comment_type' => '', + 'comment_parent' => 0, + 'user_id' => $user->ID, + 'comment_approved' => 1, + ); + + $comment_id = wp_insert_comment($comment_data); + + if ($comment_id) { + // Enviar notificação por email + sts_notify_ticket_response($post_id, $comment_id); + } + } +} +add_action('save_post_ticket', 'sts_save_ticket_data', 10, 1); + +// Personalizar colunas na lista de tickets +function sts_custom_ticket_columns($columns) { + $new_columns = array( + 'cb' => $columns['cb'], + 'ticket_id' => __('ID', 'simple-ticket-system'), + 'title' => $columns['title'], + 'author' => __('Autor', 'simple-ticket-system'), + 'ticket_type' => __('Tipo', 'simple-ticket-system'), + 'status' => __('Status', 'simple-ticket-system'), + 'date' => $columns['date'] + ); + + return $new_columns; +} +add_filter('manage_ticket_posts_columns', 'sts_custom_ticket_columns'); + +function sts_custom_ticket_column_data($column, $post_id) { + switch ($column) { + case 'status': + $status = wp_get_object_terms($post_id, 'ticket_status', array('fields' => 'names')); + if (!empty($status)) { + $status_class = sanitize_title($status[0]); + echo '' . esc_html($status[0]) . ''; + } else { + echo __('Nenhum status definido', 'simple-ticket-system'); + } + break; + case 'ticket_id': + $ticket_id = get_post_meta($post_id, '_sts_ticket_id', true); + echo $ticket_id ? '' . esc_html($ticket_id) . '' : ''; + break; + } +} +add_action('manage_ticket_posts_custom_column', 'sts_custom_ticket_column_data', 10, 2); \ No newline at end of file diff --git a/includes/assets.php b/includes/assets.php new file mode 100644 index 0000000..b9e13a9 --- /dev/null +++ b/includes/assets.php @@ -0,0 +1,25 @@ +post_type) && $screen->post_type == 'ticket') { + wp_enqueue_style('sts-admin-style', STS_URL . 'assets/css/admin.css', array(), STS_VERSION); + } +} +add_action('admin_enqueue_scripts', 'sts_enqueue_admin_assets'); \ No newline at end of file diff --git a/includes/frontend.php b/includes/frontend.php new file mode 100644 index 0000000..c58505d --- /dev/null +++ b/includes/frontend.php @@ -0,0 +1,99 @@ +' . __('Você precisa estar logado para abrir um ticket.', 'simple-ticket-system') . '

'; + echo wp_login_form(array('echo' => false)); + } + + return ob_get_clean(); +} +add_shortcode('ticket_form', 'sts_ticket_form_shortcode'); + +// Shortcode para visualização de tickets do usuário +function sts_my_tickets_shortcode() { + ob_start(); + + if (is_user_logged_in()) { + include STS_PATH . 'templates/view-ticket.php'; + } else { + echo '

' . __('Você precisa estar logado para visualizar seus tickets.', 'simple-ticket-system') . '

'; + echo wp_login_form(array('echo' => false)); + } + + return ob_get_clean(); +} +add_shortcode('my_tickets', 'sts_my_tickets_shortcode'); + +// Processar envio do formulário de ticket +function sts_process_ticket_form() { + if (isset($_POST['sts_submit_ticket']) && wp_verify_nonce($_POST['sts_ticket_form_nonce'], 'sts_ticket_form')) { + $current_user = wp_get_current_user(); + $title = sanitize_text_field($_POST['sts_ticket_title']); + $content = wp_kses_post($_POST['sts_ticket_content']); + $type_id = isset($_POST['sts_ticket_type']) ? (int) $_POST['sts_ticket_type'] : 0; + + $post_data = array( + 'post_title' => $title, + 'post_content' => $content, + 'post_status' => 'publish', + 'post_type' => 'ticket', + 'post_author' => $current_user->ID + ); + + $post_id = wp_insert_post($post_data); + + if (!is_wp_error($post_id)) { + // Gerar e salvar o ID personalizado + $last_id = get_option('sts_last_ticket_id', 0); + $new_id = $last_id + 1; + $ticket_id_formatted = 'DTI-' . sprintf('%03d', $new_id); + + update_post_meta($post_id, '_sts_ticket_id', $ticket_id_formatted); + update_option('sts_last_ticket_id', $new_id); + + // Definir status padrão + wp_set_object_terms($post_id, 'Aberto', 'ticket_status'); + + // Definir tipo de solicitação + if ($type_id > 0) { + wp_set_object_terms($post_id, $type_id, 'ticket_type'); + } + + // Lidar com o anexo + if (!empty($_FILES['sts_ticket_attachment']['name'])) { + require_once(ABSPATH . 'wp-admin/includes/file.php'); + require_once(ABSPATH . 'wp-admin/includes/media.php'); + require_once(ABSPATH . 'wp-admin/includes/image.php'); + + $attachment_id = media_handle_upload('sts_ticket_attachment', $post_id); + } + + // Enviar notificação + sts_notify_new_ticket($post_id); + + // Redireciona para a página do shortcode, não para o permalink do novo post. + wp_redirect(add_query_arg('ticket_submitted', 'success', wp_get_referer())); + exit; + } else { + wp_redirect(add_query_arg('ticket_submitted', 'error', wp_get_referer())); + exit; + } + } +} +add_action('init', 'sts_process_ticket_form'); + +// Carregar o template para a página de um único ticket +function sts_single_ticket_template($single_template) { + global $post; + + if ($post->post_type == 'ticket') { + $single_template = STS_PATH . 'templates/single-ticket.php'; + } + return $single_template; +} +add_filter('single_template', 'sts_single_ticket_template'); \ No newline at end of file diff --git a/includes/notifications.php b/includes/notifications.php new file mode 100644 index 0000000..e2ee9d2 --- /dev/null +++ b/includes/notifications.php @@ -0,0 +1,51 @@ +post_author); + $admin_email = get_option('admin_email'); + + $subject = __('Novo Ticket Criado', 'simple-ticket-system') . ': ' . $post->post_title; + + $message = __("Um novo ticket foi criado no sistema.\n\n", 'simple-ticket-system'); + $message .= __('Título: ', 'simple-ticket-system') . $post->post_title . "\n"; + $message .= __('Autor: ', 'simple-ticket-system') . $author->display_name . "\n"; + $message .= __('Email: ', 'simple-ticket-system') . $author->user_email . "\n"; + $message .= __('Conteúdo: ', 'simple-ticket-system') . "\n" . $post->post_content . "\n\n"; + $message .= __('Para visualizar e responder ao ticket, acesse: ', 'simple-ticket-system') . admin_url('post.php?post=' . $post_id . '&action=edit') . "\n"; + + wp_mail($admin_email, $subject, $message); +} + +// Notificar sobre resposta ao ticket +function sts_notify_ticket_response($post_id, $comment_id) { + $post = get_post($post_id); + $comment = get_comment($comment_id); + $author = get_userdata($post->post_author); + + // Notificar o autor do ticket + $subject = __('Nova Resposta no Seu Ticket', 'simple-ticket-system') . ': ' . $post->post_title; + + $message = __("Seu ticket recebeu uma nova resposta.\n\n", 'simple-ticket-system'); + $message .= __('Ticket: ', 'simple-ticket-system') . $post->post_title . "\n"; + $message .= __('Resposta de: ', 'simple-ticket-system') . $comment->comment_author . "\n"; + $message .= __('Resposta: ', 'simple-ticket-system') . "\n" . $comment->comment_content . "\n\n"; + $message .= __('Para visualizar o ticket completo, acesse: ', 'simple-ticket-system') . get_permalink($post_id) . "\n"; + + wp_mail($author->user_email, $subject, $message); + + // Se a resposta não foi do admin, notificar o admin + if (!user_can($comment->user_id, 'manage_options')) { + $admin_email = get_option('admin_email'); + $subject = __('Nova Resposta no Ticket', 'simple-ticket-system') . ': ' . $post->post_title; + + $message = __("Um ticket recebeu uma nova resposta.\n\n", 'simple-ticket-system'); + $message .= __('Ticket: ', 'simple-ticket-system') . $post->post_title . "\n"; + $message .= __('Autor do Ticket: ', 'simple-ticket-system') . $author->display_name . "\n"; + $message .= __('Resposta de: ', 'simple-ticket-system') . $comment->comment_author . "\n"; + $message .= __('Resposta: ', 'simple-ticket-system') . "\n" . $comment->comment_content . "\n\n"; + $message .= __('Para visualizar e responder ao ticket, acesse: ', 'simple-ticket-system') . admin_url('post.php?post=' . $post_id . '&action=edit') . "\n"; + + wp_mail($admin_email, $subject, $message); + } +} \ No newline at end of file diff --git a/includes/post-types.php b/includes/post-types.php new file mode 100644 index 0000000..3a7ae14 --- /dev/null +++ b/includes/post-types.php @@ -0,0 +1,107 @@ + __('Tickets', 'simple-ticket-system'), + 'singular_name' => __('Ticket', 'simple-ticket-system'), + 'menu_name' => __('Tickets', 'simple-ticket-system'), + 'name_admin_bar' => __('Ticket', 'simple-ticket-system'), + 'add_new' => __('Novo Ticket', 'simple-ticket-system'), + 'add_new_item' => __('Adicionar Novo Ticket', 'simple-ticket-system'), + 'new_item' => __('Novo Ticket', 'simple-ticket-system'), + 'edit_item' => __('Editar Ticket', 'simple-ticket-system'), + 'view_item' => __('Ver Ticket', 'simple-ticket-system'), + 'all_items' => __('Todos os Tickets', 'simple-ticket-system'), + 'search_items' => __('Procurar Tickets', 'simple-ticket-system'), + 'not_found' => __('Nenhum ticket encontrado.', 'simple-ticket-system'), + 'not_found_in_trash' => __('Nenhum ticket na lixeira.', 'simple-ticket-system') + ); + + $args = array( + 'labels' => $labels, + 'public' => true, + 'has_archive' => false, + 'publicly_queryable' => true, + 'show_ui' => true, + 'show_in_menu' => true, + 'query_var' => true, + 'rewrite' => array('slug' => 'ticket'), + 'capability_type' => 'post', + 'capabilities' => array( + 'create_posts' => false, // Remover capacidade de criar diretamente + ), + 'map_meta_cap' => true, + 'hierarchical' => false, + 'menu_position' => null, + 'supports' => array('title', 'editor', 'author'), + 'menu_icon' => 'dashicons-tickets' + ); + + register_post_type('ticket', $args); + + // Registrar taxonomia para status + $status_labels = array( + 'name' => __('Status', 'simple-ticket-system'), + 'singular_name' => __('Status', 'simple-ticket-system'), + 'search_items' => __('Procurar Status', 'simple-ticket-system'), + 'all_items' => __('Todos os Status', 'simple-ticket-system'), + 'edit_item' => __('Editar Status', 'simple-ticket-system'), + 'update_item' => __('Atualizar Status', 'simple-ticket-system'), + 'add_new_item' => __('Adicionar Novo Status', 'simple-ticket-system'), + 'new_item_name' => __('Novo Nome de Status', 'simple-ticket-system'), + 'menu_name' => __('Status', 'simple-ticket-system'), + ); + + $status_args = array( + 'hierarchical' => true, + 'labels' => $status_labels, + 'show_ui' => true, + 'show_admin_column' => true, + 'query_var' => true, + 'rewrite' => array('slug' => 'ticket-status'), + ); + + register_taxonomy('ticket_status', 'ticket', $status_args); + + // Registrar taxonomia para Tipo de Solicitação + $type_labels = array( + 'name' => __('Tipos de Solicitação', 'simple-ticket-system'), + 'singular_name' => __('Tipo de Solicitação', 'simple-ticket-system'), + 'search_items' => __('Procurar Tipos', 'simple-ticket-system'), + 'all_items' => __('Todos os Tipos', 'simple-ticket-system'), + 'edit_item' => __('Editar Tipo', 'simple-ticket-system'), + 'update_item' => __('Atualizar Tipo', 'simple-ticket-system'), + 'add_new_item' => __('Adicionar Novo Tipo', 'simple-ticket-system'), + 'new_item_name' => __('Novo Nome de Tipo', 'simple-ticket-system'), + 'menu_name' => __('Tipos de Solicitação', 'simple-ticket-system'), + ); + + $type_args = array( + 'hierarchical' => true, + 'labels' => $type_labels, + 'show_ui' => true, + 'show_admin_column' => true, + 'query_var' => true, + 'rewrite' => array('slug' => 'ticket-type'), + ); + + register_taxonomy('ticket_type', 'ticket', $type_args); + + // Adicionar status padrão + $default_statuses = array('Aberto', 'Em Andamento', 'Resolvido'); + + foreach ($default_statuses as $status) { + if (!term_exists($status, 'ticket_status')) { + wp_insert_term($status, 'ticket_status'); + } + } + + // Adicionar tipos padrão + $default_types = array('Dúvida', 'Problema Técnico', 'Sugestão'); + foreach ($default_types as $type) { + if (!term_exists($type, 'ticket_type')) { + wp_insert_term($type, 'ticket_type'); + } + } +} +add_action('init', 'sts_register_ticket_post_type'); \ No newline at end of file diff --git a/referencia.md b/referencia.md new file mode 100644 index 0000000..9a2c4cc --- /dev/null +++ b/referencia.md @@ -0,0 +1,189 @@ +### 1. **Variáveis CSS (Cores, Sombra, etc.)** + +As variáveis são essenciais para a consistência do design e permitem que você altere rapidamente o estilo de toda a aplicação ao modificar o valor das variáveis. Algumas variáveis importantes que podem ser reutilizadas são: + +```css +:root { + --azul-principal: #006494; + --azul-escuro: #003554; + --azul-claro: #0582ca; + --azul-brilhante: #00a6fb; + --amarelo: #ffb700; + --cinza-claro: #f8f9fa; + --cinza: #e9ecef; + --preto: #212529; + --branco: #ffffff; + --sombra: 0 4px 6px rgba(0, 0, 0, 0.1); + --borda-radius: 8px; + --transicao: all 0.3s ease; +} +``` + +Essas cores e efeitos podem ser reutilizados para manter o design consistente. + +### 2. **Tipografia** + +Fontes e tamanhos de texto podem ser extraídos e usados em outros projetos. Por exemplo: + +```css +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; +} +``` + +Você também tem diferentes tamanhos de fonte definidos nas regras de texto, como para o título de posts: + +```css +.post-title { + font-size: 2.2rem; + color: var(--azul-escuro); +} +``` + +### 3. **Layout Responsivo** + +Esse código já tem um design responsivo com várias quebras de mídia. Você pode utilizar os padrões de grid e as regras de `@media` para garantir que seu projeto seja adaptável a diferentes tamanhos de tela. Um exemplo é o layout de grade no `.grid-layout`: + +```css +.grid-layout { + display: grid; + grid-template-columns: 250px 1fr; + gap: 30px; +} +``` + +E a adaptação responsiva para telas menores: + +```css +@media (max-width: 900px) { + .grid-layout { + display: block; + } +} +``` + +### 4. **Estilos para Botões e Links** + +Estilos para botões (`btn-primario`) e links (`.nav-principal a`, `.ver-tudo`) estão bem definidos e podem ser reutilizados: + +```css +.btn-primario { + background: var(--branco); + color: var(--azul-principal); + padding: 12px 25px; + border-radius: var(--borda-radius); + font-weight: 600; + text-decoration: none; + transition: var(--transicao); +} + +.nav-principal a:hover:after { + width: 100%; +} +``` + +Esses estilos garantem que o comportamento dos links e botões seja consistente, com efeitos de transição. + +### 5. **Efeitos de Sombra e Transição** + +As sombras e transições são definidos com variáveis e podem ser facilmente reutilizados: + +```css +.sombra { + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); +} +.sombra-hover { + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); +} +``` + +### 6. **Card Layouts (ex: Atalhos, Notícias)** + +O estilo de cards, como no caso dos atalhos e das notícias, é bastante modular e pode ser reutilizado em diversos projetos. Por exemplo: + +```css +.atalho-card { + background: var(--branco); + border-radius: var(--borda-radius); + padding: 25px 15px; + text-align: center; + box-shadow: var(--sombra); + cursor: pointer; +} + +.atalho-card:hover { + transform: translateY(-5px); + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); +} +``` + +### 7. **Estilos de Ícones** + +Estilos de ícones também são importantes, especialmente para se integrar com o Font Awesome: + +```css +.atalho-card i { + font-size: 2rem; + color: var(--azul-principal); + margin-bottom: 15px; +} +``` + +### 8. **Componentes de Cabeçalho e Rodapé** + +Esses componentes têm um estilo bem definido, com a possibilidade de facilmente aplicar em outros projetos: + +```css +.header-moderno { + background: var(--branco); + box-shadow: var(--sombra); + position: sticky; + top: 0; + z-index: 100; +} + +.footer-moderno { + background: var(--azul-escuro); + color: var(--branco); + padding: 50px 0 20px; +} +``` + +### 9. **Animações** + +As animações, como o efeito `fadeIn`, são reutilizáveis e podem ser aplicadas a outros elementos: + +```css +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.noticia-card, .stats-card, .atalho-card { + animation: fadeIn 0.5s ease-out forwards; +} +``` + +--- + +### Resumo: + +Você pode reutilizar e adaptar os seguintes elementos para outros projetos: + +1. **Cores e Variáveis CSS** (cores principais, bordas, transições) +2. **Fontes e Tipografia** (fontes, tamanhos de texto) +3. **Layouts Responsivos** (uso de `@media` e grids) +4. **Botões e Links** (estilos interativos e transições) +5. **Sombreamento e Transições** (efeitos de sombra, hover) +6. **Card Layouts** (para atalhos, notícias, etc.) +7. **Ícones** (estilo e cores dos ícones) +8. **Componentes de Cabeçalho e Rodapé** (design de cabeçalhos fixos e rodapés) +9. **Animações** (efeitos de entrada, transições animadas) + + diff --git a/simple-ticket-system.php b/simple-ticket-system.php new file mode 100644 index 0000000..20fdf3c --- /dev/null +++ b/simple-ticket-system.php @@ -0,0 +1,49 @@ +' . __('Ticket enviado com sucesso!', 'simple-ticket-system') . ''; + } else { + echo '
' . __('Ocorreu um erro ao enviar o ticket.', 'simple-ticket-system') . '
'; + } +} +?> + +
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + +
+ + +
+ +
+ +
+
\ No newline at end of file diff --git a/templates/single-ticket.php b/templates/single-ticket.php new file mode 100644 index 0000000..14536e3 --- /dev/null +++ b/templates/single-ticket.php @@ -0,0 +1,66 @@ + + +
+
+ + 'names')); + $types = wp_get_object_terms(get_the_ID(), 'ticket_type', array('fields' => 'names')); + $attachments = get_attached_media('', get_the_ID()); + $ticket_id = get_post_meta(get_the_ID(), '_sts_ticket_id', true); + + $current_status = !empty($status) ? esc_html($status[0]) : __('Sem status', 'simple-ticket-system'); + $status_class = !empty($status) ? sanitize_title($status[0]) : 'no-status'; + $current_type = !empty($types) ? esc_html($types[0]) : __('Não definido', 'simple-ticket-system'); + ?> + +
> +
+
+

+ +
+
+ + + +
+
+ +
+

+ +
+ + +
+

+ +
+ + +
+ +
+ +
+ + + +
+
+ + \ No newline at end of file diff --git a/templates/view-ticket.php b/templates/view-ticket.php new file mode 100644 index 0000000..30aac30 --- /dev/null +++ b/templates/view-ticket.php @@ -0,0 +1,47 @@ + 'ticket', + 'author' => $current_user->ID, + 'posts_per_page' => -1, + 'orderby' => 'date', + 'order' => 'DESC' +); + +$tickets = get_posts($args); + +if ($tickets) { + echo '

' . __('Meus Tickets', 'simple-ticket-system') . '

'; + echo '
'; + + foreach ($tickets as $ticket) { + $status = wp_get_object_terms($ticket->ID, 'ticket_status', array('fields' => 'names')); + $types = wp_get_object_terms($ticket->ID, 'ticket_type', array('fields' => 'names')); + $ticket_id = get_post_meta($ticket->ID, '_sts_ticket_id', true); + + $status_class = !empty($status) ? sanitize_title($status[0]) : 'no-status'; + $current_status = !empty($status) ? esc_html($status[0]) : __('Sem status', 'simple-ticket-system'); + $current_type = !empty($types) ? esc_html($types[0]) : __('Não definido', 'simple-ticket-system'); + + echo ''; + echo '' . esc_html($ticket_id) . ''; + echo '
'; + echo '

' . esc_html($ticket->post_title) . '

'; + echo '' . $current_status . ''; + echo '
'; + + echo '
'; + echo '

' . __('Tipo:', 'simple-ticket-system') . ' ' . $current_type . '

'; + echo '
'; + + echo ''; + + echo '
'; + } + + echo '
'; +} else { + echo '

' . __('Você não possui tickets.', 'simple-ticket-system') . '

'; +} \ No newline at end of file