UNPKG

@pedrohrs/deep-service-desk-widget

Version:

Widget Vue.js para integração com o sistema Deep Service Desk - Versão limpa sem console.logs - Compatível com Vue 2.7.16 e Vue 3 - Com botão flutuante automático

1,381 lines (1,202 loc) 52.7 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=eb70f2fe 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, [ _cache[11] || (_cache[11] = (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("h3", null, "Novo Ticket", -1)), (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[13] || (_cache[13] = (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createStaticVNode)("<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>", 1)), (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("div", _hoisted_4, [ _cache[12] || (_cache[12] = (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", onKeypress: _cache[2] || (_cache[2] = (...args) => ($options.handleUuidKeypress && $options.handleUuidKeypress(...args))) }, null, 544), [ [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" }, " ✅ Configurar UUID ") ]) ])) : (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" }, [ (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createElementVNode)("div", _hoisted_7, [ _cache[14] || (_cache[14] = (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" }, null, 512), [ [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[15] || (_cache[15] = (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", rows: "4" }, null, 512), [ [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 }, (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.toDisplayString)($data.isSubmitting ? 'Enviando...' : 'Criar Ticket'), 9, _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=eb70f2fe ;// ./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: { apiUrl: { type: String, default: null }, clientUuid: { type: String, default: null } }, 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 com prioridade: prop > config global > variável de ambiente > padrão computedApiUrl() { let apiUrl = this.apiUrl || this.deepServiceDeskConfig.apiUrl; if (!apiUrl) { // Verificar variáveis de ambiente de forma segura if (false) // removed by dead control flow {} if (!apiUrl && typeof window !== 'undefined') { apiUrl = window.DEEP_SERVICE_DESK_API_URL; } if (!apiUrl && typeof process !== 'undefined' && ({"NODE_ENV":"production","BASE_URL":"/"})) { apiUrl = ({"NODE_ENV":"production","BASE_URL":"/"}).VUE_APP_API_URL || ({"NODE_ENV":"production","BASE_URL":"/"}).VITE_API_URL; } } return apiUrl || 'http://localhost:3000/api'; }, // 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 = ''; } } }); ;// ./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=eb70f2fe&lang=css // extracted by mini-css-extract-plugin ;// ./src/components/TicketWidget.vue?vue&type=style&index=0&id=eb70f2fe&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=94502478 const FloatingTicketWidgetvue_type_template_id_94502478_hoisted_1 = { class: "floating-ticket-widget" } function FloatingTicketWidgetvue_type_template_id_94502478_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_94502478_hoisted_1, [ (0,external_commonjs_vue_commonjs2_vue_root_Vue_namespaceObject.createVNode)(_component_TicketWidget, { ref: "ticketWidget", onTicketCreated: $options.onTicketCreated, onTicketError: $options.onTicketError }, null, 8, ["onTicketCreated", "onTicketError"]) ])) } ;// ./src/components/FloatingTicketWidget.vue?vue&type=template&id=94502478 ;// ./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 }, 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=94502478&lang=css // extracted by mini-css-extract-plugin ;// ./src/components/FloatingTicketWidget.vue?vue&type=style&index=0&id=94502478&lang=css ;// ./src/components/FloatingTicketWidget.vue ; const FloatingTicketWidget_exports_ = /*#__PURE__*/(0,exportHelper/* default */.A)(FloatingTicketWidgetvue_type_script_lang_js, [['render',FloatingTicketWidgetvue_type_template_id_94502478_render]]) /* harmony default export */ const FloatingTicketWidget = (FloatingTicketWidget_exports_); ;// ./src/index.js // Variável global para armazenar a configuração let globalConfig = null // 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 criar o botão flutuante function createFloatingButton(config) { // Verificar se o botão já existe if (document.getElementById('deep-service-desk-floating-btn')) { return } // 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 button.style.cssText = ` position: fixed; bottom: 20px; right: 20px; width: 60px; height: 60px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border: none; border-radius: 50%; color: white; cursor: pointer; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); z-index: 9998; display: 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) } // 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 --> <div class="widget-header" style=" display: flex; justify-content: space-between; align-items: center; padding: 20px 24px; border-bottom: 1px solid #e5e7eb; background-color: #f9fafb; border-radius: 8px 8px 0 0; "> <h3 style="margin: 0; color: #1f2937; font-size: 18px; font-weight: 600;">Novo Ticket de Suporte</h3> <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; ">&times;</button> </div> <!-- Formulário de Ticket --> <div class="widget-form" style="padding: 24px;"> <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; " 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; " 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> <span style="margin-left: 8px; color: #6b7280; font-size: 14px;">Enviando...</span> </div> <!-- Botões --> <div style=" display: flex; gap: 12px; justify-content: flex-end; margin-top: 20px; "> <button type="button" class="widget-close" style=" padding: 10px 20px; border: 1px solid #d1d5db; background-color: white; color: #374151; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 500; " > Cancelar </button> <button type="submit" id="submit-ticket" style=" padding: 10px 20px; border: none; background-color: #3b82f6; color: white; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 500; " > Enviar Ticket </button> </div> </form> </div> </div> </div> </div> <!-- CSS para animação de loading --> <style> @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } </style> ` // Adicionar event listeners const widget = container.querySelector('#independent-ticket-widget') const overlay = container.querySelector('.widget-overlay') const closeButtons = container.querySelectorAll('.widget-close') const form = container.querySelector('#ticket-form') const loadingDiv = container.querySelector('#form-loading') const submitButton = container.querySelector('#submit-ticket') // Função para fechar o widget const closeWidget = () => { widget.style.display = 'none' // Limpar formulário form.reset() loadingDiv.style.display = 'none' submitButton.disabled = false } // Event listeners para fechar closeButtons.forEach(btn => { btn.addEventListener('click', closeWidget) }) overlay.addEventListener('click', (e) => { if (e.target === overlay) closeWidget() }) // Event listener para envio do formulário form.addEventListener('submit', async (e) => { e.preventDefault() // Mostrar loading loadingDiv.style.display = 'block' submitButton.disabled = true // Garantir que temos a configuração mais atualizada const currentConfig = finalConfig || globalConfig if (!currentConfig || !currentConfig.clientUuid) { console.error('❌ ERRO: Não foi possível obter clientUuid!') console.error('❌ finalConfig:', finalConfig) console.error('❌ globalConfig:', globalConfig) showNotification('Erro: Configuração inválida. UUID do cliente não encontrado.', 'error') loadingDiv.style.display = 'none' submitButton.disabled = false return } // Coletar dados do formulário const formData = { title: document.getElementById('ticket-title').value, description: document.getElementById('ticket-description').value, type: 'suporte', status: 'pendente', priority: 'media', clientUuid: currentConfig.clientUuid } try { // Fazer requisição para a API const response = await fetch(`${currentConfig.apiUrl}/tickets/external`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(formData) }) // Verificar se a resposta é JSON válida const contentType = response.headers.get('content-type') if (!contentType || !contentType.includes('application/json')) { const textResponse = await response.text() console.error('❌ Resposta não é JSON:', textResponse) throw new Error(`Resposta inválida do servidor: ${textResponse}`) } if (response.ok) { const result = await response.json() // Mostrar notificação de sucesso showNotification('Ticket criado com sucesso! Você receberá uma resposta em breve.', 'success') // Fechar widget closeWidget() // Disparar evento global window.dispatchEvent(new CustomEvent('ticket-created', { detail: result })) } else { const errorText = await response.text() console.error('❌ Erro na resposta:', errorText) // Tentar fazer parse do JSON de erro let errorData try { errorData = JSON.parse(errorText) } catch (e) { errorData = { error: errorText } } throw new Error(`Erro ${response.status}: ${response.statusText} - ${errorData.error || errorText}`) } } catch (error) { console.error('❌ Erro ao criar ticket:', error) console.error('❌ Stack trace:', error.stack) console.error('❌ Dados que causaram erro:', formData) console.error('❌ Config usada:', currentConfig) // Mostrar notificação de erro showNotification(`Erro ao enviar ticket: ${error.message}`, 'error') // Disparar evento de erro window.dispatchEvent(new CustomEvent('ticket-error', { detail: error })) } finally { // Esconder loading loadingDiv.style.display = 'none' submitButton.disabled = false } }) // Listener para o evento new-ticket window.addEventListener('new-ticket', () => { // Sempre mostrar o formulário completo widget.style.display = 'block' // Focar no primeiro campo setTimeout(() => { document.getElementById('ticket-title').focus() }, 100) }) } // Função para mostrar notificações function showNotification(message, type = 'info') { // Criar notificação toast 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 se não existir 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) } // Plugin de configuração global compatível com Vue 2 e Vue 3 const DeepServiceDeskPlugin = { install(app, options = {}) { // Configurações globais - CORREÇÃO: garantir que clientUuid não seja null const config = { apiUrl: options.apiUrl || 'http://localhost:3000/api', clientUuid: options.clientUuid || null, showFloatingButton: options.showFloatingButton !== false, // true por padrão ...options } // VALIDAÇÃO CRÍTICA: verificar se clientUuid foi fornecido if (!config.clientUuid) { console.error('❌ ERRO CRÍTICO: clientUuid é obrigatório!') console.error('❌ Configuração recebida:', options) console.error('❌ Configuração processada:', config) throw new Error('Deep Service Desk Widget: clientUuid é obrigatório na configuração') } // Salvar configuração globalmente globalConfig = config // Detectar versão do Vue const isVue3 = app.version && app.version.startsWith('3') if (isVue3) { // V