@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
JavaScript
/******/ (() => { // 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;
">×</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