UNPKG

@deeprocket/deep-service-desk-widget

Version:

Widget Vue.js para integração com o sistema Deep Service Desk - Controle de visibilidade por URLs - Compatível com Vue 2.7.16 e Vue 3 - Com botão flutuante automático

1,341 lines (1,163 loc) 62.5 kB
/******/ (() => { // webpackBootstrap /******/ "use strict"; /******/ var __webpack_modules__ = ({ /***/ 262: /***/ ((__unused_webpack_module, exports) => { var __webpack_unused_export__; __webpack_unused_export__ = ({ value: true }); // runtime helper for setting properties on components // in a tree-shakable way exports.A = (sfc, props) => { const target = sfc.__vccOpts || sfc; for (const [key, val] of props) { target[key] = val; } return target; }; /***/ }) /******/ }); /************************************************************************/ /******/ // The module cache /******/ var __webpack_module_cache__ = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ var cachedModule = __webpack_module_cache__[moduleId]; /******/ if (cachedModule !== undefined) { /******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = __webpack_module_cache__[moduleId] = { /******/ // no module.id needed /******/ // no module.loaded needed /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /************************************************************************/ /******/ /* webpack/runtime/define property getters */ /******/ (() => { /******/ // define getter functions for harmony exports /******/ __webpack_require__.d = (exports, definition) => { /******/ for(var key in definition) { /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); /******/ } /******/ } /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/hasOwnProperty shorthand */ /******/ (() => { /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) /******/ })(); /******/ /******/ /* webpack/runtime/make namespace object */ /******/ (() => { /******/ // define __esModule on exports /******/ __webpack_require__.r = (exports) => { /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); /******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/publicPath */ /******/ (() => { /******/ __webpack_require__.p = ""; /******/ })(); /******/ /************************************************************************/ var __webpack_exports__ = {}; // ESM COMPAT FLAG __webpack_require__.r(__webpack_exports__); // EXPORTS __webpack_require__.d(__webpack_exports__, { FloatingTicketWidget: () => (/* reexport */ FloatingTicketWidget), TicketWidget: () => (/* reexport */ TicketWidget), "default": () => (/* binding */ entry_lib) }); ;// ./node_modules/@vue/cli-service/lib/commands/build/setPublicPath.js /* eslint-disable no-var */ // This file is imported into lib/wc client bundles. if (typeof window !== 'undefined') { var currentScript = window.document.currentScript if (false) // removed by dead control flow { var getCurrentScript; } var src = currentScript && currentScript.src.match(/(.+\/)[^/]+\.js(\?.*)?$/) if (src) { __webpack_require__.p = src[1] // eslint-disable-line } } // Indicate to webpack that this file can be concatenated /* harmony default export */ const setPublicPath = (null); ;// external {"commonjs":"vue","commonjs2":"vue","root":"Vue"} const external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject = require("vue"); ;// ./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/TicketWidget.vue?vue&type=template&id=109adfc9 const _hoisted_1 = { class: "ticket-widget" } const _hoisted_2 = { class: "popup-header" } const _hoisted_3 = { key: 0, class: "uuid-config-section" } const _hoisted_4 = { class: "form-group" } const _hoisted_5 = { key: 0, class: "error-message" } const _hoisted_6 = { class: "form-actions" } const _hoisted_7 = { class: "form-group" } const _hoisted_8 = { class: "form-group" } const _hoisted_9 = { key: 0, class: "error-message" } const _hoisted_10 = { class: "form-actions" } const _hoisted_11 = ["disabled"] function render(_ctx, _cache, $props, $setup, $data, $options) { return ((0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.openBlock)(), (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementBlock)("div", _hoisted_1, [ ($data.showPopup) ? ((0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.openBlock)(), (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementBlock)("div", { key: 0, class: "popup-overlay", onClick: _cache[10] || (_cache[10] = (...args) => ($options.closePopup && $options.closePopup(...args))) }, [ (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("div", { class: "popup-content", onClick: _cache[9] || (_cache[9] = (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.withModifiers)(() => {}, ["stop"])) }, [ (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("div", _hoisted_2, [ (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("button", { class: "close-btn", onClick: _cache[0] || (_cache[0] = (...args) => ($options.closePopup && $options.closePopup(...args))) }, "×") ]), (!$options.computedClientUuid) ? ((0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.openBlock)(), (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementBlock)("div", _hoisted_3, [ _cache[12] || (_cache[12] = (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createStaticVNode)("<div class=\"content-header\"><h3>Precisa de ajuda?</h3><p>Fale com um de nossos especialistas</p></div><div class=\"config-header\"><span class=\"config-icon\">🔧</span><div class=\"config-details\"><div class=\"config-title\">Configuração Necessária</div><div class=\"config-subtitle\">Configure o UUID do cliente para continuar</div></div></div>", 2)), (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("div", _hoisted_4, [ _cache[11] || (_cache[11] = (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("label", { for: "uuidInput" }, "UUID do Cliente *", -1)), (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.withDirectives)((0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("input", { id: "uuidInput", "onUpdate:modelValue": _cache[1] || (_cache[1] = $event => (($data.uuidInput) = $event)), type: "text", placeholder: "Digite o UUID do cliente (ex: 1c9eb621-b56c-43e4-b04b-b1a91b0a1809)", class: "form-input uuid-input", style: (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.normalizeStyle)($options.inputFocusStyle), onKeypress: _cache[2] || (_cache[2] = (...args) => ($options.handleUuidKeypress && $options.handleUuidKeypress(...args))) }, null, 36), [ [external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.vModelText, $data.uuidInput] ]) ]), ($data.uuidError) ? ((0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.openBlock)(), (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementBlock)("div", _hoisted_5, (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.toDisplayString)($data.uuidError), 1)) : (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createCommentVNode)("", true), (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("div", _hoisted_6, [ (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("button", { type: "button", onClick: _cache[3] || (_cache[3] = (...args) => ($options.closePopup && $options.closePopup(...args))), class: "btn btn-cancel" }, " Cancelar "), (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("button", { type: "button", onClick: _cache[4] || (_cache[4] = (...args) => ($options.setClientUuid && $options.setClientUuid(...args))), class: "btn btn-submit", style: (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.normalizeStyle)($options.submitButtonStyle) }, " ✅ Configurar UUID ", 4) ]) ])) : (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createCommentVNode)("", true), ($options.computedClientUuid) ? ((0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.openBlock)(), (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementBlock)("form", { key: 1, onSubmit: _cache[8] || (_cache[8] = (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.withModifiers)((...args) => ($options.submitTicket && $options.submitTicket(...args)), ["prevent"])), class: "ticket-form" }, [ _cache[15] || (_cache[15] = (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("div", { class: "content-header" }, [ (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("h3", null, "Precisa de ajuda?"), (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("p", null, "Fale com um de nossos especialistas") ], -1)), (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("div", _hoisted_7, [ _cache[13] || (_cache[13] = (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("label", { for: "title" }, "Título *", -1)), (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.withDirectives)((0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("input", { id: "title", "onUpdate:modelValue": _cache[5] || (_cache[5] = $event => (($data.ticket.title) = $event)), type: "text", required: "", placeholder: "Digite o título do ticket", class: "form-input", style: (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.normalizeStyle)($options.inputFocusStyle) }, null, 4), [ [external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.vModelText, $data.ticket.title] ]) ]), (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("div", _hoisted_8, [ _cache[14] || (_cache[14] = (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("label", { for: "description" }, "Descrição *", -1)), (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.withDirectives)((0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("textarea", { id: "description", "onUpdate:modelValue": _cache[6] || (_cache[6] = $event => (($data.ticket.description) = $event)), required: "", placeholder: "Descreva o problema ou solicitação", class: "form-textarea", style: (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.normalizeStyle)($options.inputFocusStyle), rows: "4" }, null, 4), [ [external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.vModelText, $data.ticket.description] ]) ]), ($data.errorMessage) ? ((0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.openBlock)(), (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementBlock)("div", _hoisted_9, (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.toDisplayString)($data.errorMessage), 1)) : (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createCommentVNode)("", true), (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("div", _hoisted_10, [ (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("button", { type: "button", onClick: _cache[7] || (_cache[7] = (...args) => ($options.closePopup && $options.closePopup(...args))), class: "btn btn-cancel" }, " Cancelar "), (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("button", { type: "submit", class: "btn btn-submit", disabled: $data.isSubmitting, style: (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.normalizeStyle)($options.submitButtonStyle) }, (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.toDisplayString)($data.isSubmitting ? 'Enviando...' : 'Criar Ticket'), 13, _hoisted_11) ]) ], 32)) : (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createCommentVNode)("", true) ]) ])) : (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createCommentVNode)("", true) ])) } ;// ./src/components/TicketWidget.vue?vue&type=template&id=109adfc9 ;// ./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/TicketWidget.vue?vue&type=script&lang=js /* harmony default export */ const TicketWidgetvue_type_script_lang_js = ({ name: 'TicketWidget', inject: { deepServiceDeskConfig: { from: 'deepServiceDeskConfig', default: () => ({}) } }, props: { clientUuid: { type: String, default: null }, brandColor: { type: String, default: '#3b82f6' } }, data() { return { showPopup: false, isSubmitting: false, userToken: null, userEmail: null, client: null, errorMessage: '', ticket: { title: '', description: '', type: 'support', priority: 'medium' }, uuidInput: '', uuidError: null, configuredClientUuid: null } }, computed: { // URL da API - sempre fixa computedApiUrl() { return 'https://servicedesk.deeprocket.com.br/api'; }, // Cores derivadas do brandColor computedBrandColor() { return this.brandColor || '#3b82f6'; }, computedBrandColorHover() { // Criar uma versão mais escura da cor para hover return this.darkenColor(this.computedBrandColor, 0.1); }, computedBrandColorFocus() { // Criar uma versão com transparência para o foco return this.hexToRgba(this.computedBrandColor, 0.1); }, // Estilos dinâmicos para os botões submitButtonStyle() { return { backgroundColor: this.computedBrandColor, borderColor: this.computedBrandColor }; }, inputFocusStyle() { return { '--brand-color': this.computedBrandColor, '--brand-color-focus': this.computedBrandColorFocus }; }, // UUID do cliente com prioridade: configurado manualmente > prop > config global > variável de ambiente computedClientUuid() { let clientUuid = this.configuredClientUuid || this.clientUuid || this.deepServiceDeskConfig.clientUuid; if (!clientUuid) { // Verificar variáveis de ambiente de forma segura if (false) // removed by dead control flow {} if (!clientUuid && typeof window !== 'undefined') { clientUuid = window.DEEP_SERVICE_DESK_CLIENT_UUID; } if (!clientUuid && typeof process !== 'undefined' && ({"NODE_ENV":"production","BASE_URL":"/"})) { clientUuid = ({"NODE_ENV":"production","BASE_URL":"/"}).VUE_APP_CLIENT_UUID || ({"NODE_ENV":"production","BASE_URL":"/"}).VITE_CLIENT_UUID; } } return clientUuid || null; } }, mounted() { // Escuta o evento 'new-ticket' para abrir o popup window.addEventListener('new-ticket', this.handleNewTicketEvent) // Tentar carregar UUID de variáveis de ambiente (opcional) this.loadClientUuidFromEnv() }, beforeDestroy() { window.removeEventListener('new-ticket', this.handleNewTicketEvent) }, methods: { // Função para obter token de autenticação do sistema externo getAuthTokenFromExternalSystem() { try { // Busca tokens em ordem de prioridade const tokenTc = localStorage.getItem('token-tc'); if (tokenTc) return tokenTc; const tokenDeep = localStorage.getItem('token-deep'); if (tokenDeep) return tokenDeep; const authToken = localStorage.getItem('authToken'); if (authToken) return authToken; return null; } catch (error) { console.error('Erro ao acessar token:', error); return null; } }, // Função para obter email do usuário do sistema externo getUserEmailFromExternalSystem() { try { // Busca o usuário do sistema externo (user-tc) const userTcToken = localStorage.getItem('user-tc'); if (userTcToken) { try { const userData = JSON.parse(atob(userTcToken)); return userData.email || null; } catch (decodeError) { // Silenciar erro de decodificação } } // Fallback para outros sistemas const userDeep = localStorage.getItem('user-deep'); if (userDeep) { try { const userData = JSON.parse(atob(userDeep)); return userData.email || null; } catch (decodeError) { // Silenciar erro de decodificação } } return null; } catch (error) { console.error('Erro ao acessar dados do usuário:', error); return null; } }, // Função para buscar cliente pelo UUID async fetchClientByUuid() { if (!this.userToken || !this.computedClientUuid) return null; try { const response = await fetch(`${this.computedApiUrl}/clients/find-by-uuid?uuid=${encodeURIComponent(this.computedClientUuid)}`, { headers: { 'Authorization': `Bearer ${this.userToken}`, 'Content-Type': 'application/json' } }); if (response.ok) { const client = await response.json(); return client; } else if (response.status === 404) { return null; } else { return null; } } catch (error) { console.error('Erro ao buscar cliente por UUID:', error); return null; } }, // Função para buscar cliente pelo email async findClientByEmail(email) { if (!email || !this.userToken) return null; try { const response = await fetch(`${this.computedApiUrl}/clients/find-by-email?email=${encodeURIComponent(email)}`, { headers: { 'Authorization': `Bearer ${this.userToken}`, 'Content-Type': 'application/json' } }); if (response.ok) { const client = await response.json(); return client; } else if (response.status === 404) { // Cliente não encontrado - isso é normal return null; } else { return null; } } catch (error) { console.error('Erro ao buscar cliente por email:', error); return null; } }, // Função para buscar clientes async fetchClients() { if (!this.userToken) return try { const response = await fetch(`${this.computedApiUrl}/clients`, { headers: { 'Authorization': `Bearer ${this.userToken}`, 'Content-Type': 'application/json' } }) if (response.ok) { const data = await response.json() this.clients = data.data || data } } catch (error) { console.error('Erro ao buscar clientes:', error) } }, // Função para abrir o popup async openPopup() { // Se UUID não estiver configurado, apenas abre o popup para configuração if (!this.computedClientUuid) { this.errorMessage = ''; this.showPopup = true; return; } // Se UUID já estiver configurado, inicializa o widget await this.initializeWidget(); if (this.client) { this.errorMessage = ''; this.showPopup = true; } else { this.errorMessage = 'Erro ao configurar widget. Verifique o UUID e autenticação.'; } }, // Função para fechar o popup closePopup() { this.showPopup = false this.resetForm() this.errorMessage = '' }, // Função para resetar o formulário resetForm() { this.ticket.title = '' this.ticket.description = '' this.ticket.type = 'support' this.ticket.priority = 'medium' }, // Função para submeter o ticket async submitTicket() { if (!this.userToken) { this.errorMessage = 'Token de autenticação não encontrado'; return; } if (!this.client) { this.errorMessage = 'Cliente não configurado'; return; } this.isSubmitting = true; this.errorMessage = ''; try { const ticketData = { title: this.ticket.title.trim(), description: this.ticket.description.trim(), type: this.ticket.type, priority: this.ticket.priority, status: 'open', clientId: this.client.id }; // Adicionar informações do usuário se disponível if (this.userEmail) { ticketData.description += `\n\n--- Informações do Usuário ---\nEmail: ${this.userEmail}`; } const response = await fetch(`${this.computedApiUrl}/tickets`, { method: 'POST', headers: { 'Authorization': `Bearer ${this.userToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify(ticketData) }); const responseData = await response.json(); if (response.ok) { // Sucesso - emite evento com dados do ticket criado this.$emit('ticket-created', { ...responseData, success: true, message: 'Ticket criado com sucesso!' }); // Fecha o popup e reseta o form this.closePopup(); } else { // Erro da API const errorMsg = responseData.error || 'Erro ao criar ticket'; this.errorMessage = errorMsg; this.$emit('ticket-error', { message: errorMsg, details: responseData }); } } catch (error) { console.error('Erro ao criar ticket:', error); const errorMsg = 'Erro de conexão. Verifique sua internet e tente novamente.'; this.errorMessage = errorMsg; this.$emit('ticket-error', { message: errorMsg, details: error.message }); } finally { this.isSubmitting = false; } }, // Handler para o evento customizado handleNewTicketEvent() { this.openPopup() }, // Método público para verificar se está aberto (para uso via ref) isOpen() { return this.showPopup }, // Método para carregar o UUID do cliente de variáveis de ambiente loadClientUuidFromEnv() { // Tentar carregar de diferentes fontes de variáveis de ambiente let envUuid = null; // Verificar se estamos em ambiente de desenvolvimento com Vite if (false) // removed by dead control flow {} // Verificar variável global do window if (!envUuid && typeof window !== 'undefined') { envUuid = window.DEEP_SERVICE_DESK_CLIENT_UUID; } // Verificar se process está disponível (apenas em desenvolvimento) if (!envUuid && typeof process !== 'undefined' && ({"NODE_ENV":"production","BASE_URL":"/"})) { envUuid = ({"NODE_ENV":"production","BASE_URL":"/"}).VUE_APP_CLIENT_UUID || ({"NODE_ENV":"production","BASE_URL":"/"}).VITE_CLIENT_UUID; } if (!envUuid) { return; } // Validação básica de UUID const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; if (!uuidRegex.test(envUuid)) { console.error('❌ UUID do cliente inválido:', envUuid); return; } this.configuredClientUuid = envUuid; // Inicializar widget automaticamente se UUID for carregado de variável de ambiente this.initializeWidget(); }, handleUuidKeypress(event) { if (event.key === 'Enter') { this.setClientUuid(); } }, setClientUuid() { const uuid = this.uuidInput.trim(); if (!uuid) { this.uuidError = 'Por favor, digite um UUID'; return; } // Validação básica de UUID const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; if (!uuidRegex.test(uuid)) { this.uuidError = 'UUID inválido. Formato esperado: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'; return; } this.configuredClientUuid = uuid; this.uuidInput = ''; this.uuidError = null; // Buscar cliente automaticamente após configurar UUID this.initializeWidget(); }, async initializeWidget() { // Busca token de autenticação this.userToken = this.getAuthTokenFromExternalSystem(); if (!this.userToken) { this.uuidError = 'Token de autenticação não encontrado. Faça login no sistema.'; this.configuredClientUuid = null; return; } // Busca email do usuário this.userEmail = this.getUserEmailFromExternalSystem(); // Busca cliente pelo UUID this.client = await this.fetchClientByUuid(); if (!this.client) { this.uuidError = 'Cliente não encontrado. Verifique se o UUID está correto.'; this.configuredClientUuid = null; return; } }, resetUuid() { this.configuredClientUuid = null; this.client = null; this.userToken = null; this.userEmail = null; this.uuidInput = ''; this.uuidError = null; this.errorMessage = ''; }, // Métodos para manipulação de cores hexToRgba(hex, alpha = 1) { const r = parseInt(hex.slice(1, 3), 16); const g = parseInt(hex.slice(3, 5), 16); const b = parseInt(hex.slice(5, 7), 16); return `rgba(${r}, ${g}, ${b}, ${alpha})`; }, darkenColor(hex, amount) { const num = parseInt(hex.replace('#', ''), 16); const amt = Math.round(2.55 * amount * 100); const R = (num >> 16) - amt; const G = (num >> 8 & 0x00FF) - amt; const B = (num & 0x0000FF) - amt; return '#' + (0x1000000 + (R < 0 ? 0 : R) * 0x10000 + (G < 0 ? 0 : G) * 0x100 + (B < 0 ? 0 : B)).toString(16).slice(1); } } }); ;// ./src/components/TicketWidget.vue?vue&type=script&lang=js ;// ./node_modules/mini-css-extract-plugin/dist/loader.js??clonedRuleSet-12.use[0]!./node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!./node_modules/vue-loader/dist/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/TicketWidget.vue?vue&type=style&index=0&id=109adfc9&lang=css // extracted by mini-css-extract-plugin ;// ./src/components/TicketWidget.vue?vue&type=style&index=0&id=109adfc9&lang=css // EXTERNAL MODULE: ./node_modules/vue-loader/dist/exportHelper.js var exportHelper = __webpack_require__(262); ;// ./src/components/TicketWidget.vue ; const __exports__ = /*#__PURE__*/(0,exportHelper/* default */.A)(TicketWidgetvue_type_script_lang_js, [['render',render]]) /* harmony default export */ const TicketWidget = (__exports__); ;// ./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/FloatingTicketWidget.vue?vue&type=template&id=f38110dc const FloatingTicketWidgetvue_type_template_id_f38110dc_hoisted_1 = { class: "floating-ticket-widget" } function FloatingTicketWidgetvue_type_template_id_f38110dc_render(_ctx, _cache, $props, $setup, $data, $options) { const _component_TicketWidget = (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.resolveComponent)("TicketWidget") return ((0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.openBlock)(), (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementBlock)("div", FloatingTicketWidgetvue_type_template_id_f38110dc_hoisted_1, [ (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createVNode)(_component_TicketWidget, { ref: "ticketWidget", "brand-color": $props.brandColor, onTicketCreated: $options.onTicketCreated, onTicketError: $options.onTicketError }, null, 8, ["brand-color", "onTicketCreated", "onTicketError"]) ])) } ;// ./src/components/FloatingTicketWidget.vue?vue&type=template&id=f38110dc ;// ./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/FloatingTicketWidget.vue?vue&type=script&lang=js /* harmony default export */ const FloatingTicketWidgetvue_type_script_lang_js = ({ name: 'FloatingTicketWidget', components: { TicketWidget: TicketWidget }, props: { brandColor: { type: String, default: '#3b82f6' } }, inject: { deepServiceDeskConfig: { from: 'deepServiceDeskConfig', default: () => ({}) } }, mounted() { // Escutar evento global para abrir o widget window.addEventListener('new-ticket', this.openWidget) }, beforeDestroy() { window.removeEventListener('new-ticket', this.openWidget) }, methods: { openWidget() { if (this.$refs.ticketWidget) { this.$refs.ticketWidget.openPopup() } }, onTicketCreated(data) { // Emitir evento global para que a aplicação possa escutar window.dispatchEvent(new CustomEvent('ticket-created', { detail: data })) // Mostrar notificação de sucesso this.showNotification('Ticket criado com sucesso!', 'success') }, onTicketError(error) { // Emitir evento global para que a aplicação possa escutar window.dispatchEvent(new CustomEvent('ticket-error', { detail: error })) // Mostrar notificação de erro this.showNotification(error.message || 'Erro ao criar ticket', 'error') }, showNotification(message, type = 'info') { // Criar notificação toast simples const notification = document.createElement('div') notification.className = `deep-service-desk-notification ${type}` notification.textContent = message notification.style.cssText = ` position: fixed; top: 20px; right: 20px; background: ${type === 'success' ? '#10b981' : type === 'error' ? '#ef4444' : '#3b82f6'}; color: white; padding: 12px 20px; border-radius: 8px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); z-index: 10000; font-size: 14px; font-weight: 500; max-width: 300px; word-wrap: break-word; animation: slideInRight 0.3s ease; ` // Adicionar animação CSS if (!document.getElementById('deep-service-desk-notification-styles')) { const style = document.createElement('style') style.id = 'deep-service-desk-notification-styles' style.textContent = ` @keyframes slideInRight { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes slideOutRight { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } } ` document.head.appendChild(style) } document.body.appendChild(notification) // Remover após 5 segundos setTimeout(() => { notification.style.animation = 'slideOutRight 0.3s ease' setTimeout(() => { if (notification.parentNode) { notification.parentNode.removeChild(notification) } }, 300) }, 5000) } } }); ;// ./src/components/FloatingTicketWidget.vue?vue&type=script&lang=js ;// ./node_modules/mini-css-extract-plugin/dist/loader.js??clonedRuleSet-12.use[0]!./node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!./node_modules/vue-loader/dist/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/FloatingTicketWidget.vue?vue&type=style&index=0&id=f38110dc&lang=css // extracted by mini-css-extract-plugin ;// ./src/components/FloatingTicketWidget.vue?vue&type=style&index=0&id=f38110dc&lang=css ;// ./src/components/FloatingTicketWidget.vue ; const FloatingTicketWidget_exports_ = /*#__PURE__*/(0,exportHelper/* default */.A)(FloatingTicketWidgetvue_type_script_lang_js, [['render',FloatingTicketWidgetvue_type_template_id_f38110dc_render]]) /* harmony default export */ const FloatingTicketWidget = (FloatingTicketWidget_exports_); ;// ./src/index.js // Variável global para armazenar a configuração let globalConfig = null // Variável para controlar se o monitoramento já foi configurado let urlMonitoringConfigured = false // Função global para forçar verificação de visibilidade (pode ser chamada externamente) window.deepServiceDeskUpdateVisibility = function() { if (globalConfig) { updateButtonVisibility(globalConfig) } } // Função para detectar a versão do Vue function getVueVersion() { if (typeof window !== 'undefined' && window.Vue) { return window.Vue.version || '2' } // Em ambiente de build, tentar detectar pela estrutura return '3' // Padrão para Vue 3 } // Função para verificar se a URL atual deve ocultar o botão function shouldHideButtonOnCurrentUrl(hiddenUrls) { if (!hiddenUrls || !Array.isArray(hiddenUrls) || hiddenUrls.length === 0) { return false } const currentUrl = window.location.href const currentPath = window.location.pathname return hiddenUrls.some(urlPattern => { // Se é uma string simples, verificar se é substring da URL ou path if (typeof urlPattern === 'string') { return currentUrl.includes(urlPattern) || currentPath.includes(urlPattern) } // Se é uma expressão regular if (urlPattern instanceof RegExp) { return urlPattern.test(currentUrl) || urlPattern.test(currentPath) } return false }) } // Função para criar o botão flutuante function createFloatingButton(config) { // Verificar se o botão já existe if (document.getElementById('deep-service-desk-floating-btn')) { // Se o botão já existe, apenas atualizar a visibilidade updateButtonVisibility(config) return } // SEMPRE criar o botão, mas definir visibilidade inicial baseada na URL const shouldHideInitially = shouldHideButtonOnCurrentUrl(config.hiddenUrls) // Criar o botão flutuante const button = document.createElement('button') button.id = 'deep-service-desk-floating-btn' button.innerHTML = ` <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M20 2H4C2.9 2 2 2.9 2 4V22L6 18H20C21.1 18 22 17.1 22 16V4C22 2.9 21.1 2 20 2ZM20 16H5.17L4 17.17V4H20V16Z" fill="currentColor"/> <path d="M7 9H17V11H7V9ZM7 12H17V14H7V12ZM7 6H17V8H7V6Z" fill="currentColor"/> </svg> ` // Estilos do botão (incluindo visibilidade inicial) const brandColor = config.brandColor || '#3b82f6' button.style.cssText = ` position: fixed; bottom: 20px; right: 20px; width: 60px; height: 60px; background: ${brandColor}; border: none; border-radius: 50%; color: white; cursor: pointer; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); z-index: 9998; display: ${shouldHideInitially ? 'none' : 'flex'}; align-items: center; justify-content: center; transition: all 0.3s ease; font-size: 0; ` // Efeitos hover button.addEventListener('mouseenter', () => { button.style.transform = 'scale(1.1)' button.style.boxShadow = '0 6px 25px rgba(0, 0, 0, 0.2)' }) button.addEventListener('mouseleave', () => { button.style.transform = 'scale(1)' button.style.boxShadow = '0 4px 20px rgba(0, 0, 0, 0.15)' }) // Evento de clique button.addEventListener('click', () => { window.dispatchEvent(new CustomEvent('new-ticket')) }) // Adicionar tooltip button.title = 'Abrir Suporte' // Inserir no DOM document.body.appendChild(button) // Adicionar estilos responsivos const style = document.createElement('style') style.textContent = ` @media (max-width: 768px) { #deep-service-desk-floating-btn { bottom: 15px !important; right: 15px !important; width: 50px !important; height: 50px !important; } } ` document.head.appendChild(style) // SEMPRE configurar monitoramento de URL (não apenas quando há hiddenUrls) setupUrlMonitoring(config, button) } // Função para atualizar visibilidade do botão function updateButtonVisibility(config) { const button = document.getElementById('deep-service-desk-floating-btn') if (button) { // SEMPRE verificar - se não há hiddenUrls, o botão deve ser visível const shouldHide = shouldHideButtonOnCurrentUrl(config.hiddenUrls) button.style.display = shouldHide ? 'none' : 'flex' // Debug apenas em localhost if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') { console.log('[Deep Service Desk] updateButtonVisibility:') console.log(' URL atual:', window.location.href) console.log(' hiddenUrls:', config.hiddenUrls) console.log(' shouldHide:', shouldHide) console.log(' display:', button.style.display) } } } // Função separada para configurar monitoramento de URL function setupUrlMonitoring(config, button) { // Se não há URLs ocultas, não precisa monitorar if (!config.hiddenUrls || config.hiddenUrls.length === 0) { return } // Se monitoramento já foi configurado, não configurar novamente if (urlMonitoringConfigured) { return } urlMonitoringConfigured = true let lastUrl = window.location.href const checkUrlAndToggleButton = () => { const currentUrl = window.location.href // Só verificar se a URL realmente mudou if (currentUrl !== lastUrl) { lastUrl = currentUrl } // Usar a função unificada para atualizar visibilidade updateButtonVisibility(config) } // Múltiplas estratégias para detectar mudanças de URL // 1. Interceptar pushState e replaceState const originalPushState = history.pushState const originalReplaceState = history.replaceState history.pushState = function() { originalPushState.apply(history, arguments) setTimeout(checkUrlAndToggleButton, 50) setTimeout(checkUrlAndToggleButton, 200) // Fallback adicional } history.replaceState = function() { originalReplaceState.apply(history, arguments) setTimeout(checkUrlAndToggleButton, 50) setTimeout(checkUrlAndToggleButton, 200) // Fallback adicional } // 2. Escutar eventos de navegação window.addEventListener('popstate', () => { setTimeout(checkUrlAndToggleButton, 50) }) window.addEventListener('hashchange', () => { setTimeout(checkUrlAndToggleButton, 50) }) // 3. Observer para mudanças no DOM (pode indicar mudança de página) if (typeof MutationObserver !== 'undefined') { const observer = new MutationObserver(() => { // Verificar se a URL mudou if (window.location.href !== lastUrl) { setTimeout(checkUrlAndToggleButton, 100) } }) observer.observe(document.body, { subtree: true, childList: true }) } // 4. Verificação periódica como fallback (a cada 2 segundos) setInterval(() => { if (window.location.href !== lastUrl) { checkUrlAndToggleButton() } }, 2000) // 5. Escutar eventos customizados que podem indicar navegação window.addEventListener('beforeunload', checkUrlAndToggleButton) window.addEventListener('load', () => { setTimeout(checkUrlAndToggleButton, 100) }) // 6. Detectar frameworks populares e escutar eventos específicos // Vue Router if (window.Vue || document.querySelector('[data-v-]')) { // Escutar eventos do Vue Router window.addEventListener('vue-router-navigation', () => { setTimeout(checkUrlAndToggleButton, 50) }) } // React Router if (window.React || document.querySelector('[data-reactroot]')) { // Escutar mudanças no React const checkReactNavigation = () => { setTimeout(checkUrlAndToggleButton, 50) } window.addEventListener('react-router-navigation', checkReactNavigation) } // 7. Interceptar fetch e XMLHttpRequest para detectar possíveis navegações AJAX const originalFetch = window.fetch if (originalFetch) { window.fetch = function() { const result = originalFetch.apply(this, arguments) result.then(() => { // Verificar após requests AJAX (pode indicar mudança de página) setTimeout(() => { if (window.location.href !== lastUrl) { checkUrlAndToggleButton() } }, 300) }).catch(() => { // Mesmo em caso de erro, verificar setTimeout(() => { if (window.location.href !== lastUrl) { checkUrlAndToggleButton() } }, 300) }) return result } } // Verificação inicial setTimeout(checkUrlAndToggleButton, 100) } // Função para remover o botão flutuante function removeFloatingButton() { const button = document.getElementById('deep-service-desk-floating-btn') if (button) { button.remove() } } // Variável global para armazenar a instância do Vue let globalVueInstance = null // Função para criar a instância global do widget function createGlobalWidget(config, isVue3, vueApp) { // Verificar se já existe if (document.getElementById('deep-service-desk-global-widget')) { return } // Criar container para o widget const container = document.createElement('div') container.id = 'deep-service-desk-global-widget' document.body.appendChild(container) // SEMPRE usar o widget independente com formulário completo // Isso garante que o formulário sempre funcione, independente do Vue createIndependentWidget(config) } // Função para criar um widget independente (sem Vue) function createIndependentWidget(config) { // Garantir que temos acesso à configuração global const finalConfig = config || globalConfig if (!finalConfig || !finalConfig.clientUuid) { console.error('❌ ERRO CRÍTICO: Configuração não possui clientUuid!') console.error('❌ Config recebida:', config) console.error('❌ Global config:', globalConfig) return } const container = document.getElementById('deep-service-desk-global-widget') if (!container) return // Criar HTML do widget com formulário completo container.innerHTML = ` <div id="independent-ticket-widget" style="display: none;"> <!-- Overlay do modal --> <div class="widget-overlay" style=" position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 9999; "> <div class="widget-modal" style=" background: white; border-radius: 8px; width: 90%; max-width: 500px; max-height: 90vh; overflow-y: auto; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); "> <!-- Header simplificado --> <div class="widget-header" style=" display: flex; justify-content: flex-end; align-items: center; padding: 15px 20px; border-bottom: 1px solid #e5e7eb; background-color: #f9fafb; border-radius: 8px 8px 0 0; "> <button class="widget-close" style=" background: none; border: none; font-size: 24px; cursor: pointer; color: #6b7280; padding: 0; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; border-radius: 4px; transition: background-color 0.2s; ">&times;</button> </div> <!-- Formulário de Ticket --> <div class="widget-form" style="padding: 24px;"> <!-- Título e subtítulo dentro do conteúdo --> <div style="text-align: center; margin-bottom: 24px;"> <h3 style="margin: 0 0 8px 0; color: #1f2937; font-size: 20px; font-weight: 600;">Precisa de ajuda?</h3> <p style="margin: 0; color: #6b7280; font-size: 14px;">Fale com um de nossos especialistas</p> </div> <form id="ticket-form"> <!-- Título --> <div style="margin-bottom: 16px;"> <label style="display: block; margin-bottom: 6px; font-weight: 500; color: #374151; font-size: 14px;"> Título * </label> <input type="text" id="ticket-title" required style=" width: 100%; padding: 10px 12px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 14px; box-sizing: border-box; transition: border-color 0.2s, box-shadow 0.2s; " placeholder="Resumo do problema ou solicitação" /> </div> <!-- Descrição --> <div style="margin-bottom: 20px;"> <label style="display: block; margin-bottom: 6px; font-weight: 500; color: #374151; font-size: 14px;"> Descrição * </label> <textarea id="ticket-description" required rows="6" style=" width: 100%; padding: 10px 12px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 14px; box-sizing: border-box; resize: vertical; min-height: 120px; transition: border-color 0.2s, box-shadow 0.2s; " placeholder="Descreva detalhadamente o problema ou solicitação..." ></textarea> </div> <!-- Loading indicator --> <div id="form-loading" style="display: none; text-align: center; margin: 16px 0;"> <div style=" display: inline-block; width: 20px; height: 20px; border: 2px solid #e5e7eb; border-top: 2px solid #3b82f6; border-radius: 50%; animation: spin 1s linear infinite; "></div>