@jtandrelevicius/utils-js-library 1.0.5 → 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,365 +1,9 @@
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
- }
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';
363
7
 
364
8
  const lib = {
365
9
  ServicoDados,
@@ -367,17 +11,24 @@ const lib = {
367
11
  UtilitariosOrdenacao,
368
12
  ServicoExportacao,
369
13
 
370
- // Aliases
371
- JSK: ServicoDados,
14
+ // Aliases
15
+ JSK: ServicoDados,
372
16
  JFSK: UtilitariosFormatacao,
373
17
  JOSK: UtilitariosOrdenacao,
374
18
  JEXSK: ServicoExportacao
375
19
  };
376
20
 
377
- if (typeof module !== 'undefined' && module.exports) {
378
- module.exports = lib;
379
- } 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') {
380
30
  Object.assign(window, lib);
31
+
381
32
  if (typeof window.executeQuery === 'function') {
382
33
  ServicoDados.definirExecutor(window.executeQuery);
383
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.5",
3
+ "version": "1.0.7",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {