@jtandrelevicius/utils-js-library 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -1,542 +1,34 @@
1
1
  'use strict';
2
2
 
3
- class ServicoDados {
4
-
5
- /**
6
- * Define o executor de query padrão para a classe (Legado).
7
- * @param {Function} funcaoExecutor Função que aceita (query, params, onSuccess, onError).
8
- */
9
- static definirExecutor(funcaoExecutor) {
10
- this.executor = funcaoExecutor;
11
- }
12
-
13
- /**
14
- * Realiza requisições do tipo POST (Base para salvar/excluir).
15
- * @param {string} url URL da requisição.
16
- * @param {Object} corpo Corpo da requisição.
17
- * @param {Object} opcoes Opções adicionais (headers, raw).
18
- * @returns {Promise<Object>} Resposta da requisição.
19
- */
20
- static async post(url, corpo, { headers, raw } = { headers: {}, raw: false }) {
21
- let isJSON = true;
22
-
23
- if (headers) {
24
- const cabecalhoTipoOriginal = headers['Content-Type'] ? String(headers['Content-Type']) : 'application/json; charset=UTF-8';
25
- isJSON = headers['Content-Type'] ? RegExp(/json/i).exec(headers['Content-Type']) : isJSON;
26
-
27
- if (headers['Content-Type']) delete headers['Content-Type'];
28
- headers['Content-Type'] = cabecalhoTipoOriginal;
29
- }
30
-
31
- try {
32
- let corpoRequisicaoFormatado = corpo;
33
-
34
- if (corpo && typeof corpo === 'object') {
35
- corpoRequisicaoFormatado = JSON.stringify(corpo);
36
- }
37
-
38
- if (typeof window === 'undefined' || !window.fetch) {
39
- throw new Error("O método 'post' requer um ambiente de navegador com 'fetch' disponível.");
40
- }
41
-
42
- const resposta = await window.fetch.bind(window)(url, {
43
- headers,
44
- method: 'POST',
45
- redirect: 'follow',
46
- credentials: 'include',
47
- body: corpoRequisicaoFormatado
48
- });
49
-
50
- if (raw) {
51
- return resposta;
52
- }
53
-
54
- return isJSON ? resposta.json() : resposta.text();
55
-
56
- } catch (erro) {
57
- console.error("[ServicoDados] Erro no POST:", erro);
58
- throw erro;
59
- }
60
- }
61
-
62
- /**
63
- * Normaliza a resposta da API (Método auxiliar).
64
- * @private
65
- */
66
- static normalizarResposta(valorBruto) {
67
- try {
68
- const valorParseado = typeof valorBruto === 'string' ? JSON.parse(valorBruto) : valorBruto;
69
-
70
- if (!valorParseado) return [];
71
- if (Array.isArray(valorParseado)) return valorParseado;
72
-
73
- let dadosFinais = null;
74
-
75
- if (typeof valorParseado === 'object') {
76
- if (typeof valorParseado.b === 'string') {
77
- dadosFinais = JSON.parse(valorParseado.b);
78
- } else if (valorParseado.c?.b && typeof valorParseado.c.b === 'string') {
79
- dadosFinais = JSON.parse(valorParseado.c.b);
80
- } else if (valorParseado.data?.responseBody || valorParseado.responseBody) {
81
- const corpo = valorParseado.data?.responseBody || valorParseado.responseBody;
82
- dadosFinais = typeof corpo === 'string' ? JSON.parse(corpo) : corpo;
83
- }
84
- }
85
-
86
- if (Array.isArray(dadosFinais)) return dadosFinais;
87
- throw new Error("Formato de resposta desconhecido ou inválido.");
88
- } catch (erro) {
89
- throw new Error(`Falha ao normalizar resposta da query: ${erro.message}`);
90
- }
91
- }
92
-
93
- /**
94
- * Executa uma consulta de dados (Via executor configurado ou executeQuery global).
95
- * @param {string} query A string da consulta.
96
- * @param {Object} [params=null] Parâmetros opcionais.
97
- * @returns {Promise<Array<Object>>} Promise resolvendo com os dados.
98
- */
99
- static async consultar(query, params = null) {
100
- const executor = this.executor || (typeof window !== 'undefined' ? window.executeQuery : null);
101
-
102
- if (typeof executor !== 'function') {
103
- return Promise.reject(new Error("Executor de query não configurado e 'executeQuery' global não encontrado."));
104
- }
105
-
106
- return new Promise((resolve, reject) => {
107
- executor(query, params,
108
- (valorBruto) => {
109
- try {
110
- const dados = ServicoDados.normalizarResposta(valorBruto);
111
- resolve(dados || []);
112
- } catch (err) {
113
- reject(err);
114
- }
115
- },
116
- (err) => reject(new Error(`Erro na execução da query: ${err}`))
117
- );
118
- });
119
- }
120
-
121
- /**
122
- * Executa uma consulta paginada.
123
- */
124
- static async consultarPaginado(query, params = null, limite, offset) {
125
- const limiteSeguro = Number(limite);
126
- const offsetSeguro = Number(offset);
127
-
128
- if (isNaN(limiteSeguro) || isNaN(offsetSeguro) || limiteSeguro < 0 || offsetSeguro < 0) {
129
- throw new Error("Parâmetros 'limite' e 'offset' devem ser números positivos válidos.");
130
- }
131
-
132
- const queryPaginada = `${query} LIMIT ${limiteSeguro} OFFSET ${offsetSeguro}`;
133
- const dados = await ServicoDados.consultar(queryPaginada, params);
134
- const paginaAtual = Math.floor(offsetSeguro / limiteSeguro) + 1;
135
-
136
- return {
137
- dados,
138
- meta: { paginaAtual, itensPorPagina: limiteSeguro, totalItens: null, totalPaginas: null }
139
- };
140
- }
141
-
142
- /**
143
- * Salva (Insert) ou Atualiza (Update) um registro usando DatasetSP.
144
- * * Lógica Automática:
145
- * 1. Se 'chavesPrimarias' for informado -> Realiza UPDATE no registro específico.
146
- * 2. Se 'chavesPrimarias' for NULO/UNDEFINED -> Realiza INSERT de um novo registro.
147
- * * @param {Object} dados Objeto com os dados a serem gravados { CAMPO: valor }.
148
- * @param {string} entidade Nome da entidade/tabela (ex: 'Parceiro', 'TGFCAB').
149
- * @param {Object} [chavesPrimarias=null] Chaves primárias { PK: valor } para edição.
150
- * @returns {Promise<Object>} Resposta da requisição.
151
- */
152
- static async salvar(dados, entidade, chavesPrimarias = null) {
153
- const url = `${window.location.origin}/mge/service.sbr?serviceName=DatasetSP.save&outputType=json`;
154
-
155
- const chavesDados = Object.keys(dados);
156
- const fields = chavesDados.map(campo => campo.toUpperCase());
157
-
158
- const values = {};
159
- chavesDados.forEach((chave, indice) => {
160
- values[indice.toString()] = String(dados[chave]);
161
- });
162
-
163
- const record = { values: values };
164
-
165
- if (chavesPrimarias) {
166
- const pk = {};
167
- Object.keys(chavesPrimarias).forEach(chave => {
168
- pk[chave.toUpperCase()] = String(chavesPrimarias[chave]);
169
- });
170
- record.pk = pk;
171
- }
172
-
173
- const dadosEnvio = {
174
- serviceName: 'DatasetSP.save',
175
- requestBody: {
176
- entityName: entidade,
177
- fields: fields,
178
- records: [record]
179
- }
180
- };
181
-
182
- return await ServicoDados.post(url, dadosEnvio);
183
- }
184
-
185
- /**
186
- * Exclui registros da base de dados.
187
- * @param {string} entidade Nome da Entidade.
188
- * @param {Object|Array} chavesPrimarias Objeto PK { COD: 1 } ou Array de PKs.
189
- * @returns {Promise<Object>} Resposta da exclusão.
190
- */
191
- static async excluir(entidade, chavesPrimarias) {
192
- const url = `${window.location.origin}/mge/service.sbr?serviceName=DatasetSP.removeRecord&outputType=json`;
193
-
194
- const dadosEnvio = {
195
- serviceName: 'DatasetSP.removeRecord',
196
- requestBody: {
197
- entityName: entidade,
198
- pks: Array.isArray(chavesPrimarias) ? chavesPrimarias : [chavesPrimarias]
199
- }
200
- };
201
-
202
- return ServicoDados.post(url, dadosEnvio);
203
- }
204
- }
205
-
206
- /**
207
- * UtilitariosFormatacao
208
- */
209
- class UtilitariosFormatacao {
210
- static moeda(valor) {
211
- const valorNumerico = parseFloat(valor);
212
- if (isNaN(valorNumerico)) return "R$ 0,00";
213
- return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(valorNumerico);
214
- }
215
-
216
- static numerico(valor) {
217
- const valorNumerico = parseFloat(valor);
218
- if (isNaN(valorNumerico)) return "0";
219
- return new Intl.NumberFormat('pt-BR', { maximumFractionDigits: 0 }).format(valorNumerico);
220
- }
221
-
222
- static decimal(valor) {
223
- const valorNumerico = parseFloat(valor);
224
- if (isNaN(valorNumerico)) return "0,00";
225
- return new Intl.NumberFormat('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(valorNumerico);
226
- }
227
-
228
- static data() {
229
- return new Intl.DateTimeFormat('pt-BR').format(new Date());
230
- }
231
-
232
- static formatarDataParaDMY(stringDataIso) {
233
- if (!stringDataIso) return '';
234
- const partes = stringDataIso.split('-');
235
- if (partes.length !== 3) return stringDataIso;
236
- return `${partes[2]}/${partes[1]}/${partes[0]}`;
237
- }
238
-
239
- static normalizarStringData(entradaData) {
240
- if (!entradaData) return '01/01/2025';
241
- let str = String(entradaData).trim();
242
-
243
- if (str.includes('/')) {
244
- const partes = str.split('/');
245
- if (partes.length >= 3) {
246
- const dia = String(parseInt(partes[0], 10)).padStart(2, '0');
247
- const mes = String(parseInt(partes[1], 10)).padStart(2, '0');
248
- const ano = partes[2].substring(0, 4);
249
- return `${dia}/${mes}/${ano}`;
250
- }
251
- }
252
-
253
- if (str.includes('-')) {
254
- const partes = str.split('-');
255
- if (partes.length === 3) {
256
- return `${partes[2].substring(0, 2)}/${partes[1]}/${partes[0]}`;
257
- }
258
- }
259
-
260
- return str;
261
- }
262
-
263
- static obterDataAtualISO() {
264
- return new Date().toISOString().split('T')[0];
265
- }
266
-
267
- static dataHora() {
268
- const agora = new Date();
269
- const parteData = new Intl.DateTimeFormat('pt-BR').format(agora);
270
- const parteHora = agora.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' });
271
- return `${parteData} ${parteHora}`;
272
- }
273
- }
274
-
275
- /**
276
- * UtilitariosOrdenacao
277
- */
278
- class UtilitariosOrdenacao {
279
- static ordenar(dados, chave, direcao = 'asc') {
280
- if (!Array.isArray(dados) || !chave) return dados;
281
- const multiplicador = direcao === 'asc' ? 1 : -1;
282
-
283
- return [...dados].sort((a, b) => {
284
- const valA = a[chave];
285
- const valB = b[chave];
286
- if (valA == null) return 1;
287
- if (valB == null) return -1;
288
- if (typeof valA === 'string' && typeof valB === 'string') {
289
- return valA.localeCompare(valB) * multiplicador;
290
- }
291
- if (valA < valB) return -1 * multiplicador;
292
- if (valA > valB) return 1 * multiplicador;
293
- return 0;
294
- });
295
- }
296
- }
297
-
298
- /**
299
- * ServicoExportacao
300
- */
301
- class ServicoExportacao {
302
- static CDN_URL = "https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js";
303
-
304
- static async carregarDependencia() {
305
- if (typeof window !== 'undefined' && window.XLSX) return;
306
- return new Promise((resolve, reject) => {
307
- const script = document.createElement('script');
308
- script.src = this.CDN_URL;
309
- script.async = true;
310
- script.onload = () => resolve();
311
- script.onerror = () => reject(new Error('Falha ao baixar dependência XLSX.'));
312
- document.head.appendChild(script);
313
- });
314
- }
315
-
316
- static async paraExcel(dados, colunas, nomeArquivo, nomePlanilha = 'Dados') {
317
- await this.carregarDependencia();
318
- const dadosFormatados = dados.map(item => {
319
- const linha = {};
320
- colunas.forEach(col => { linha[col.display] = item[col.key]; });
321
- return linha;
322
- });
323
-
324
- const planilha = XLSX.utils.json_to_sheet(dadosFormatados);
325
- const pastaTrabalho = XLSX.utils.book_new();
326
- XLSX.utils.book_append_sheet(pastaTrabalho, planilha, nomePlanilha);
327
- XLSX.writeFile(pastaTrabalho, `${nomeArquivo}.xlsx`);
328
- }
329
-
330
- static paraCSV(dados, colunas, nomeArquivo) {
331
- const linhaCabecalho = colunas.map(c => `"${c.display}"`).join(';');
332
- const linhasCorpo = dados.map(item => {
333
- return colunas.map(col => {
334
- const val = item[col.key] ?? '';
335
- const valString = String(val).replace(/"/g, '""').replace(/\r?\n|\r/g, ' ');
336
- return `"${valString}"`;
337
- }).join(';');
338
- });
339
- const conteudoCsv = [linhaCabecalho, ...linhasCorpo].join('\r\n');
340
- const blob = new Blob([conteudoCsv], { type: 'text/csv;charset=utf-8;' });
341
- const link = document.createElement('a');
342
- if (link.download !== undefined) {
343
- const url = URL.createObjectURL(blob);
344
- link.setAttribute('href', url);
345
- link.setAttribute('download', `${nomeArquivo}.csv`);
346
- link.style.visibility = 'hidden';
347
- document.body.appendChild(link);
348
- link.click();
349
- document.body.removeChild(link);
350
- }
351
- }
352
-
353
- static async exportar(dados, colunas, nomeArquivo = 'exportacao', formato = 'xlsx') {
354
- if (!dados?.length) throw new Error("Sem dados para exportar.");
355
- if (!colunas?.length) throw new Error("Colunas não definidas.");
356
-
357
- const fmt = formato.toLowerCase();
358
- if (fmt === 'xlsx') await this.paraExcel(dados, colunas, nomeArquivo);
359
- else if (fmt === 'csv') this.paraCSV(dados, colunas, nomeArquivo);
360
- else throw new Error(`Formato '${formato}' não suportado.`);
361
- }
362
- }
363
-
364
- /**
365
- * ServicoPagina
366
- * Manipuladores de Página e Navegação Sankhya-W
367
- */
368
- class ServicoPagina {
369
- /**
370
- * Retorna a URL atual da pagina com o caminho opcional.
371
- * @param { String } path Caminho a ser adicionado a URL atual
372
- * @returns { String } A URL com o protocolo HTTPS ou HTTP
373
- */
374
- static getUrl(path) {
375
- return `${window.location.origin}${path ? '/' + path.replace('/', '') : ''}`;
376
- }
377
-
378
- /**
379
- * Remove o frame da página de BI
380
- * * @param { { instancia: String, paginaInicial: String, opcoes: any } } configuracoes Configuracoes gerais da pagina
381
- * * **instancia**: Nome exato do componente de BI
382
- * * **paginaInicial**: URL (a partir da pasta raiz) e nome do arquivo da pagina inicial
383
- * * **opcoes**: [opcional] Campos com valores a serem recebidos pela pagina
384
- * * _Padrao_: `{ instancia: '', paginaInicial: 'app.jsp' }`
385
- * * @example JPSK.removerFrame ({ instancia: 'TELA_HTML5', paginaInicial: 'index.jsp'});
386
- */
387
- static removerFrame({ instancia, paginaInicial, ...opcoes } = { instancia: '', paginaInicial: 'app.jsp' }) {
388
-
389
- new Promise(resolve => {
390
-
391
- if (window.parent.document.getElementsByTagName('body').length) {
392
-
393
- if (window.parent.document.querySelector('div.gwt-PopupPanel.alert-box.box-shadow'))
394
- window.parent.document.querySelector('div.gwt-PopupPanel.alert-box.box-shadow')
395
- .style.display = 'none';
396
-
397
- window.parent.document.getElementsByTagName('body')[0].style.overflow = 'hidden';
398
- }
399
-
400
- if (window.parent.parent.document.getElementsByTagName('body').length) {
401
-
402
- if (window.parent.parent.document.querySelector('div.gwt-PopupPanel.alert-box.box-shadow'))
403
- window.parent.parent.document.querySelector('div.gwt-PopupPanel.alert-box.box-shadow')
404
- .style.display = 'none';
405
-
406
- window.parent.parent.document.getElementsByTagName('body')[0].style.overflow = 'hidden';
407
- }
408
-
409
- if (
410
- window.parent.document
411
- .querySelector('div.GI-BUHVBPVC > div > div > div > div > div > table > tbody > tr > td > div')
412
- ) {
413
- instancia = window.parent.document
414
- .querySelector('div.GI-BUHVBPVC > div > div > div > div > div > table > tbody > tr > td > div')
415
- .textContent;
416
- }
417
-
418
- if (instancia && instancia.length > 0) {
419
- ServicoDados.consultar(`SELECT NUGDG FROM TSIGDG WHERE TITULO = '${instancia}'`).
420
- then(e => resolve({ gadGetID: 'html5_z6dld', nuGdt: e[0].NUGDG, ...opcoes }));
421
- }
422
- else {
423
- resolve({ gadGetID: 'html5_z6dld', nuGdt: 0, ...opcoes });
424
- }
425
- }).
426
- then(o =>
427
- setTimeout(() => {
428
- if (typeof window.parent.document.getElementsByClassName('DashWindow')[0] != 'undefined') {
429
-
430
- const opcoesUrl =
431
- Object.
432
- keys(o).
433
- filter(item => !['params', 'UID', 'instance', 'nuGdg', 'gadGetID'].includes(item)).
434
- map(item => `&${item}=${o[item]}`).
435
- join('');
436
-
437
- const url = `/mge/html5component.mge?entryPoint=${paginaInicial}&nuGdg=${o.nuGdt}${opcoesUrl}`
438
-
439
- setTimeout(() =>
440
- window.parent.document.getElementsByClassName('dyna-gadget')[0].innerHTML =
441
- `<iframe src="${url}" class="gwt-Frame" style="width: 100%; height: 100%;"></iframe>`
442
- , 500);
443
-
444
- setTimeout(() => document.getElementsByClassName('popupContent').length
445
- ? document.getElementsByClassName('popupContent')[0].parentElement.remove()
446
- : (() => { /**/ })()
447
- , 20000);
448
-
449
- setTimeout(() => (document.getElementById('stndz-style').parentElement.parentElement)
450
- .getElementsByTagName('body')[0].style.overflow = 'hidden'
451
- , 20000);
452
- }
453
- })
454
- );
455
- }
456
-
457
- /**
458
- * Abre uma nova guia com a pagina atual
459
- *
460
- * @param { boolean } forcado - [Opcional] Indica se a abertura da nova guia deve ser forcada
461
- * * @example JPSK.novaGuia ();
462
- */
463
- static novaGuia(forcado = false) {
464
-
465
- if ((window.parent.parent.document.querySelector('.Taskbar-container') && !forcado) || forcado) {
466
- Object.assign(document.createElement('a'), { target: '_blank', href: window.location.href }).click();
467
- }
468
-
469
- }
470
-
471
- /**
472
- * Abre uma pagina dentro do Sankhya-W.
473
- * * - Se o resourceID nao existir, o sistema informara que a tela nao existe.
474
- * - Se as chaves primarias nao forem informadas, a tela sera aberta na pagina inicial.
475
- * - Se existirem chaves primarias, mas nao forem encontradas, a tela ssera aberta como visualizacao de um registro vazio (para inclusao)
476
- * - Se existirem chaves primarias e forem encontradas, a tela sera aberta no registro encontrado.
477
- * * @param { String } resourceID ID do recurso a ser aberto
478
- * @param { Object } chavesPrimarias Chaves de identificacao do registro
479
- * * @example JPSK.abrirPagina ('br.com.sankhya.core.cad.marcas', { CODIGO: 999 });
480
- */
481
- static abrirPagina(resourceID, chavesPrimarias) {
482
-
483
- let url = ServicoPagina.getUrl(`/mge/system.jsp#app/%resID`);
484
- url = url.replace('%resID', btoa(resourceID));
485
-
486
- if (chavesPrimarias) {
487
-
488
- let body = {};
489
-
490
- Object.keys(chavesPrimarias).forEach(function (chave) {
491
- body[chave] = isNaN(chavesPrimarias[chave])
492
- ? String(chavesPrimarias[chave])
493
- : Number(chavesPrimarias[chave])
494
- });
495
-
496
- url = url.concat(`/${btoa(JSON.stringify(body))}`);
497
-
498
- }
499
-
500
- Object.assign(document.createElement('a'), {
501
- target: '_top',
502
- href: url
503
- }).click();
504
-
505
- }
506
-
507
- /**
508
- * Fecha a pagina atual.
509
- * * Ele verifica se a pagina atual esta dentro do Sankhya-W para fechar, senao ele fecha a aba do navegador.
510
- */
511
- static fecharPagina() {
512
- if (window.parent.parent.document.querySelector('.Taskbar-container')) {
513
- window.parent.parent.document.querySelector(
514
- 'li.ListItem.AppItem.AppItem-selected div.Taskbar-icon.icon-close').click();
515
- } else {
516
- window.close();
517
- }
518
- }
519
- }
3
+ import { ServicoDados } from './lib/jsk-core.js';
4
+ import { UtilitariosFormatacao } from './lib/format-core.js';
5
+ import { UtilitariosOrdenacao } from './lib/order-core.js';
6
+ import { ServicoExportacao } from './lib/export-core.js';
520
7
 
521
8
  const lib = {
522
9
  ServicoDados,
523
10
  UtilitariosFormatacao,
524
11
  UtilitariosOrdenacao,
525
12
  ServicoExportacao,
526
- ServicoPagina,
527
13
 
528
- // Aliases
529
- JSK: ServicoDados,
14
+ // Aliases
15
+ JSK: ServicoDados,
530
16
  JFSK: UtilitariosFormatacao,
531
17
  JOSK: UtilitariosOrdenacao,
532
- JEXSK: ServicoExportacao,
533
- JPSK: ServicoPagina
18
+ JEXSK: ServicoExportacao
534
19
  };
535
20
 
536
- if (typeof module !== 'undefined' && module.exports) {
537
- module.exports = lib;
538
- } else if (typeof window !== 'undefined') {
21
+ export {
22
+ ServicoDados,
23
+ UtilitariosFormatacao,
24
+ UtilitariosOrdenacao,
25
+ ServicoExportacao,
26
+ lib as default
27
+ };
28
+
29
+ if (typeof window !== 'undefined') {
539
30
  Object.assign(window, lib);
31
+
540
32
  if (typeof window.executeQuery === 'function') {
541
33
  ServicoDados.definirExecutor(window.executeQuery);
542
34
  console.log("[JSK] Executor 'executeQuery' detectado e configurado automaticamente.");
@@ -0,0 +1,64 @@
1
+ 'use strict';
2
+
3
+ export class ServicoExportacao {
4
+ static CDN_URL = "https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js";
5
+
6
+ static async carregarDependencia() {
7
+ if (typeof window !== 'undefined' && window.XLSX) return;
8
+ return new Promise((resolve, reject) => {
9
+ const script = document.createElement('script');
10
+ script.src = this.CDN_URL;
11
+ script.async = true;
12
+ script.onload = () => resolve();
13
+ script.onerror = () => reject(new Error('Falha ao baixar dependência XLSX.'));
14
+ document.head.appendChild(script);
15
+ });
16
+ }
17
+
18
+ static async paraExcel(dados, colunas, nomeArquivo, nomePlanilha = 'Dados') {
19
+ await this.carregarDependencia();
20
+ const dadosFormatados = dados.map(item => {
21
+ const linha = {};
22
+ colunas.forEach(col => { linha[col.display] = item[col.key]; });
23
+ return linha;
24
+ });
25
+
26
+ const planilha = XLSX.utils.json_to_sheet(dadosFormatados);
27
+ const pastaTrabalho = XLSX.utils.book_new();
28
+ XLSX.utils.book_append_sheet(pastaTrabalho, planilha, nomePlanilha);
29
+ XLSX.writeFile(pastaTrabalho, `${nomeArquivo}.xlsx`);
30
+ }
31
+
32
+ static paraCSV(dados, colunas, nomeArquivo) {
33
+ const linhaCabecalho = colunas.map(c => `"${c.display}"`).join(';');
34
+ const linhasCorpo = dados.map(item => {
35
+ return colunas.map(col => {
36
+ const val = item[col.key] ?? '';
37
+ const valString = String(val).replace(/"/g, '""').replace(/\r?\n|\r/g, ' ');
38
+ return `"${valString}"`;
39
+ }).join(';');
40
+ });
41
+ const conteudoCsv = [linhaCabecalho, ...linhasCorpo].join('\r\n');
42
+ const blob = new Blob([conteudoCsv], { type: 'text/csv;charset=utf-8;' });
43
+ const link = document.createElement('a');
44
+ if (link.download !== undefined) {
45
+ const url = URL.createObjectURL(blob);
46
+ link.setAttribute('href', url);
47
+ link.setAttribute('download', `${nomeArquivo}.csv`);
48
+ link.style.visibility = 'hidden';
49
+ document.body.appendChild(link);
50
+ link.click();
51
+ document.body.removeChild(link);
52
+ }
53
+ }
54
+
55
+ static async exportar(dados, colunas, nomeArquivo = 'exportacao', formato = 'xlsx') {
56
+ if (!dados?.length) throw new Error("Sem dados para exportar.");
57
+ if (!colunas?.length) throw new Error("Colunas não definidas.");
58
+
59
+ const fmt = formato.toLowerCase();
60
+ if (fmt === 'xlsx') await this.paraExcel(dados, colunas, nomeArquivo);
61
+ else if (fmt === 'csv') this.paraCSV(dados, colunas, nomeArquivo);
62
+ else throw new Error(`Formato '${formato}' não suportado.`);
63
+ }
64
+ }
@@ -0,0 +1,73 @@
1
+ 'use strict';
2
+
3
+ export class UtilitariosFormatacao {
4
+ static moeda(valor) {
5
+ const valorNumerico = parseFloat(valor);
6
+ if (isNaN(valorNumerico)) return "R$ 0,00";
7
+ return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(valorNumerico);
8
+ }
9
+
10
+ static numerico(valor) {
11
+ const valorNumerico = parseFloat(valor);
12
+ if (isNaN(valorNumerico)) return "0";
13
+ return new Intl.NumberFormat('pt-BR', { maximumFractionDigits: 0 }).format(valorNumerico);
14
+ }
15
+
16
+ static decimal(valor) {
17
+ const valorNumerico = parseFloat(valor);
18
+ if (isNaN(valorNumerico)) return "0,00";
19
+ return new Intl.NumberFormat('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(valorNumerico);
20
+ }
21
+
22
+ static porcentagem(valor) {
23
+ const valorNumerico = parseFloat(valor);
24
+ if (isNaN(valorNumerico)) return "0,00 %";
25
+ return new Intl.NumberFormat('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(valorNumerico) + ' %';
26
+ }
27
+
28
+ static data() {
29
+ return new Intl.DateTimeFormat('pt-BR').format(new Date());
30
+ }
31
+
32
+ static formatarDataParaDMY(stringDataIso) {
33
+ if (!stringDataIso) return '';
34
+ const partes = stringDataIso.split('-');
35
+ if (partes.length !== 3) return stringDataIso;
36
+ return `${partes[2]}/${partes[1]}/${partes[0]}`;
37
+ }
38
+
39
+ static normalizarStringData(entradaData) {
40
+ if (!entradaData) return '01/01/2025';
41
+ let str = String(entradaData).trim();
42
+
43
+ if (str.includes('/')) {
44
+ const partes = str.split('/');
45
+ if (partes.length >= 3) {
46
+ const dia = String(parseInt(partes[0], 10)).padStart(2, '0');
47
+ const mes = String(parseInt(partes[1], 10)).padStart(2, '0');
48
+ const ano = partes[2].substring(0, 4);
49
+ return `${dia}/${mes}/${ano}`;
50
+ }
51
+ }
52
+
53
+ if (str.includes('-')) {
54
+ const partes = str.split('-');
55
+ if (partes.length === 3) {
56
+ return `${partes[2].substring(0, 2)}/${partes[1]}/${partes[0]}`;
57
+ }
58
+ }
59
+
60
+ return str;
61
+ }
62
+
63
+ static obterDataAtualISO() {
64
+ return new Date().toISOString().split('T')[0];
65
+ }
66
+
67
+ static dataHora() {
68
+ const agora = new Date();
69
+ const parteData = new Intl.DateTimeFormat('pt-BR').format(agora);
70
+ const parteHora = agora.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' });
71
+ return `${parteData} ${parteHora}`;
72
+ }
73
+ }
@@ -0,0 +1,251 @@
1
+ 'use strict';
2
+
3
+ export class ServicoDados {
4
+
5
+ // =========================================================================
6
+ // PARTE 1: MANIPULAÇÃO DE DADOS (Banco de Dados)
7
+ // =========================================================================
8
+
9
+ /**
10
+ * Define o executor de query padrão (Legado).
11
+ */
12
+ static definirExecutor(funcaoExecutor) {
13
+ this.executor = funcaoExecutor;
14
+ }
15
+
16
+ /**
17
+ * Realiza requisições POST.
18
+ */
19
+ static async post(url, corpo, { headers, raw } = { headers: {}, raw: false }) {
20
+ let isJSON = true;
21
+
22
+ if (headers) {
23
+ const cabecalhoTipoOriginal = headers['Content-Type'] ? String(headers['Content-Type']) : 'application/json; charset=UTF-8';
24
+ isJSON = headers['Content-Type'] ? RegExp(/json/i).exec(headers['Content-Type']) : isJSON;
25
+ if (headers['Content-Type']) delete headers['Content-Type'];
26
+ headers['Content-Type'] = cabecalhoTipoOriginal;
27
+ }
28
+
29
+ try {
30
+ let corpoRequisicaoFormatado = corpo;
31
+ if (corpo && typeof corpo === 'object') {
32
+ corpoRequisicaoFormatado = JSON.stringify(corpo);
33
+ }
34
+
35
+ if (typeof window === 'undefined' || !window.fetch) {
36
+ throw new Error("Requer ambiente de navegador com 'fetch'.");
37
+ }
38
+
39
+ const resposta = await window.fetch.bind(window)(url, {
40
+ headers,
41
+ method: 'POST',
42
+ redirect: 'follow',
43
+ credentials: 'include',
44
+ body: corpoRequisicaoFormatado
45
+ });
46
+
47
+ if (raw) return resposta;
48
+ return isJSON ? resposta.json() : resposta.text();
49
+
50
+ } catch (erro) {
51
+ console.error("[JSK] Erro no POST:", erro);
52
+ throw erro;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Normaliza a resposta da API.
58
+ */
59
+ static normalizarResposta(valorBruto) {
60
+ try {
61
+ const valorParseado = typeof valorBruto === 'string' ? JSON.parse(valorBruto) : valorBruto;
62
+ if (!valorParseado) return [];
63
+ if (Array.isArray(valorParseado)) return valorParseado;
64
+
65
+ let dadosFinais = null;
66
+ if (typeof valorParseado === 'object') {
67
+ if (typeof valorParseado.b === 'string') {
68
+ dadosFinais = JSON.parse(valorParseado.b);
69
+ } else if (valorParseado.c?.b && typeof valorParseado.c.b === 'string') {
70
+ dadosFinais = JSON.parse(valorParseado.c.b);
71
+ } else if (valorParseado.data?.responseBody || valorParseado.responseBody) {
72
+ const corpo = valorParseado.data?.responseBody || valorParseado.responseBody;
73
+ dadosFinais = typeof corpo === 'string' ? JSON.parse(corpo) : corpo;
74
+ }
75
+ }
76
+ if (Array.isArray(dadosFinais)) return dadosFinais;
77
+ throw new Error("Formato de resposta inválido.");
78
+ } catch (erro) {
79
+ throw new Error(`Erro ao normalizar resposta: ${erro.message}`);
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Executa consulta SQL.
85
+ */
86
+ static async consultar(query, params = null) {
87
+ const executor = this.executor || (typeof window !== 'undefined' ? window.executeQuery : null);
88
+
89
+ if (typeof executor !== 'function') {
90
+ return Promise.reject(new Error("Executor 'executeQuery' não encontrado."));
91
+ }
92
+
93
+ return new Promise((resolve, reject) => {
94
+ executor(query, params,
95
+ (valorBruto) => {
96
+ try {
97
+ const dados = ServicoDados.normalizarResposta(valorBruto);
98
+ resolve(dados || []);
99
+ } catch (err) { reject(err); }
100
+ },
101
+ (err) => reject(new Error(`Erro na query: ${err}`))
102
+ );
103
+ });
104
+ }
105
+
106
+ /**
107
+ * Executa consulta paginada.
108
+ */
109
+ static async consultarPaginado(query, params = null, limite, offset) {
110
+ const limiteSeguro = Number(limite);
111
+ const offsetSeguro = Number(offset);
112
+ if (isNaN(limiteSeguro) || isNaN(offsetSeguro) || limiteSeguro < 0) throw new Error("Parâmetros de paginação inválidos.");
113
+
114
+ const queryPaginada = `${query} LIMIT ${limiteSeguro} OFFSET ${offsetSeguro}`;
115
+ const dados = await ServicoDados.consultar(queryPaginada, params);
116
+ const paginaAtual = Math.floor(offsetSeguro / limiteSeguro) + 1;
117
+
118
+ return {
119
+ dados,
120
+ meta: { paginaAtual, itensPorPagina: limiteSeguro, totalItens: null, totalPaginas: null }
121
+ };
122
+ }
123
+
124
+ /**
125
+ * Salva (Insert/Update) via DatasetSP.
126
+ */
127
+ static async salvar(dados, entidade, chavesPrimarias = null) {
128
+ const url = `${window.location.origin}/mge/service.sbr?serviceName=DatasetSP.save&outputType=json`;
129
+ const chavesDados = Object.keys(dados);
130
+ const fields = chavesDados.map(campo => campo.toUpperCase());
131
+ const values = {};
132
+
133
+ chavesDados.forEach((chave, indice) => {
134
+ values[indice.toString()] = String(dados[chave]);
135
+ });
136
+
137
+ const record = { values: values };
138
+ if (chavesPrimarias) {
139
+ const pk = {};
140
+ Object.keys(chavesPrimarias).forEach(chave => pk[chave.toUpperCase()] = String(chavesPrimarias[chave]));
141
+ record.pk = pk;
142
+ }
143
+
144
+ return await ServicoDados.post(url, {
145
+ serviceName: 'DatasetSP.save',
146
+ requestBody: { entityName: entidade, fields: fields, records: [record] }
147
+ });
148
+ }
149
+
150
+ /**
151
+ * Exclui registro via DatasetSP.
152
+ */
153
+ static async excluir(entidade, chavesPrimarias) {
154
+ const url = `${window.location.origin}/mge/service.sbr?serviceName=DatasetSP.removeRecord&outputType=json`;
155
+ return ServicoDados.post(url, {
156
+ serviceName: 'DatasetSP.removeRecord',
157
+ requestBody: {
158
+ entityName: entidade,
159
+ pks: Array.isArray(chavesPrimarias) ? chavesPrimarias : [chavesPrimarias]
160
+ }
161
+ });
162
+ }
163
+
164
+ // =========================================================================
165
+ // PARTE 2: SERVIÇO DE PÁGINA (Interface / Navegação)
166
+ // =========================================================================
167
+
168
+ static getUrl(path) {
169
+ return `${window.location.origin}${path ? '/' + path.replace('/', '') : ''}`;
170
+ }
171
+
172
+ /**
173
+ * Remove o frame (Modo Fullscreen).
174
+ * Requer ID (nuGdt) resolvido previamente.
175
+ */
176
+ static removerFrame({ nuGdt, paginaInicial, ...opcoes } = { nuGdt: 0, paginaInicial: 'app.jsp' }) {
177
+ return new Promise(resolve => {
178
+ // Limpa alertas e scroll do parent
179
+ [window.parent.document, window.parent.parent.document].forEach(doc => {
180
+ if (doc && doc.getElementsByTagName('body').length) {
181
+ const alertBox = doc.querySelector('div.gwt-PopupPanel.alert-box.box-shadow');
182
+ if (alertBox) alertBox.style.display = 'none';
183
+ doc.getElementsByTagName('body')[0].style.overflow = 'hidden';
184
+ }
185
+ });
186
+
187
+ resolve({ gadGetID: 'html5_z6dld', nuGdt: nuGdt || 0, ...opcoes });
188
+ }).then(o =>
189
+ setTimeout(() => {
190
+ if (typeof window.parent.document.getElementsByClassName('DashWindow')[0] != 'undefined') {
191
+ const opcoesUrl = Object.keys(o)
192
+ .filter(item => !['params', 'UID', 'instance', 'nuGdg', 'gadGetID'].includes(item))
193
+ .map(item => `&${item}=${o[item]}`)
194
+ .join('');
195
+
196
+ const url = `/mge/html5component.mge?entryPoint=${paginaInicial}&nuGdg=${o.nuGdt}${opcoesUrl}`;
197
+
198
+ const gadgetDiv = window.parent.document.getElementsByClassName('dyna-gadget')[0];
199
+ if (gadgetDiv) {
200
+ gadgetDiv.innerHTML = `<iframe src="${url}" class="gwt-Frame" style="width: 100%; height: 100%;"></iframe>`;
201
+ }
202
+
203
+ // Limpa popups residuais
204
+ const popup = document.getElementsByClassName('popupContent')[0];
205
+ if (popup && popup.parentElement) popup.parentElement.remove();
206
+
207
+ // Trava scroll local
208
+ const styleEl = document.getElementById('stndz-style');
209
+ if (styleEl && styleEl.parentElement && styleEl.parentElement.parentElement) {
210
+ styleEl.parentElement.parentElement.getElementsByTagName('body')[0].style.overflow = 'hidden';
211
+ }
212
+ }
213
+ }, 500)
214
+ );
215
+ }
216
+
217
+ /**
218
+ * Abre Nova Guia.
219
+ */
220
+ static novaGuia(forcado = false) {
221
+ const isSankhya = !!window.parent.parent.document.querySelector('.Taskbar-container');
222
+ if ((isSankhya && !forcado) || forcado) {
223
+ Object.assign(document.createElement('a'), { target: '_blank', href: window.location.href }).click();
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Abre Pagina no Sankhya-W.
229
+ */
230
+ static abrirPagina(resourceID, chavesPrimarias) {
231
+ let url = ServicoDados.getUrl(`/mge/system.jsp#app/%resID`);
232
+ url = url.replace('%resID', btoa(resourceID));
233
+
234
+ if (chavesPrimarias) {
235
+ let body = {};
236
+ Object.keys(chavesPrimarias).forEach(k => body[k] = isNaN(chavesPrimarias[k]) ? String(chavesPrimarias[k]) : Number(chavesPrimarias[k]));
237
+ url = url.concat(`/${btoa(JSON.stringify(body))}`);
238
+ }
239
+
240
+ Object.assign(document.createElement('a'), { target: '_top', href: url }).click();
241
+ }
242
+
243
+ /**
244
+ * Fecha a Pagina atual.
245
+ */
246
+ static fecharPagina() {
247
+ const closeBtn = window.parent.parent.document.querySelector('li.ListItem.AppItem.AppItem-selected div.Taskbar-icon.icon-close');
248
+ if (closeBtn) closeBtn.click();
249
+ else window.close();
250
+ }
251
+ }
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ export class UtilitariosOrdenacao {
4
+ static ordenar(dados, chave, direcao = 'asc') {
5
+ if (!Array.isArray(dados) || !chave) return dados;
6
+ const multiplicador = direcao === 'asc' ? 1 : -1;
7
+
8
+ return [...dados].sort((a, b) => {
9
+ const valA = a[chave];
10
+ const valB = b[chave];
11
+ if (valA == null) return 1;
12
+ if (valB == null) return -1;
13
+ if (typeof valA === 'string' && typeof valB === 'string') {
14
+ return valA.localeCompare(valB) * multiplicador;
15
+ }
16
+ if (valA < valB) return -1 * multiplicador;
17
+ if (valA > valB) return 1 * multiplicador;
18
+ return 0;
19
+ });
20
+ }
21
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jtandrelevicius/utils-js-library",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {