@jtandrelevicius/utils-js-library 1.0.0

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.
Files changed (2) hide show
  1. package/index.js +347 -0
  2. package/package.json +11 -0
package/index.js ADDED
@@ -0,0 +1,347 @@
1
+ /**
2
+ * DataQueryService
3
+ * Responsável pela execução e processamento de consultas assíncronas.
4
+ */
5
+ class DataQueryService {
6
+ /**
7
+ * Define o executor de query padrão para a classe.
8
+ * Isso remove a dependência de uma função global.
9
+ * @param {Function} executorFunction Função que aceita (query, params, onSuccess, onError).
10
+ */
11
+ static setExecutor(executorFunction) {
12
+ this.executor = executorFunction;
13
+ }
14
+
15
+ /**
16
+ * @private
17
+ * Normaliza a resposta da API, lidando com estruturas aninhadas legadas/específicas.
18
+ * @param {unknown} rawValue O valor bruto retornado pela query.
19
+ * @returns {Array<Object>} O array de dados normalizado.
20
+ */
21
+ static normalizeResponse(rawValue) {
22
+ try {
23
+ const parsedValue = typeof rawValue === 'string' ? JSON.parse(rawValue) : rawValue;
24
+
25
+ if (!parsedValue) return [];
26
+ if (Array.isArray(parsedValue)) return parsedValue;
27
+
28
+ let finalData = null;
29
+
30
+ if (typeof parsedValue === 'object') {
31
+ if (typeof parsedValue.b === 'string') {
32
+ finalData = JSON.parse(parsedValue.b);
33
+ } else if (parsedValue.c?.b && typeof parsedValue.c.b === 'string') {
34
+ finalData = JSON.parse(parsedValue.c.b);
35
+ } else if (parsedValue.data?.responseBody || parsedValue.responseBody) {
36
+ const body = parsedValue.data?.responseBody || parsedValue.responseBody;
37
+ finalData = typeof body === 'string' ? JSON.parse(body) : body;
38
+ }
39
+ }
40
+
41
+ if (Array.isArray(finalData)) return finalData;
42
+
43
+ throw new Error("Formato de resposta desconhecido ou inválido.");
44
+ } catch (error) {
45
+ throw new Error(`Falha ao normalizar resposta da query: ${error.message}`);
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Executa uma consulta de dados.
51
+ * @param {string} query A string da consulta (SQL/HQL/etc).
52
+ * @param {Object} [params=null] Parâmetros opcionais para a query.
53
+ * @returns {Promise<Array<Object>>} Promise resolvendo com os dados.
54
+ */
55
+ static async fetch(query, params = null) {
56
+ if (typeof this.executor !== 'function') {
57
+ return Promise.reject(new Error("Executor de query não configurado. Use DataQueryService.setExecutor()."));
58
+ }
59
+
60
+ return new Promise((resolve, reject) => {
61
+ this.executor(query, params,
62
+ (rawValue) => {
63
+ try {
64
+ const data = DataQueryService.normalizeResponse(rawValue);
65
+ resolve(data || []);
66
+ } catch (err) {
67
+ reject(err);
68
+ }
69
+ },
70
+ (err) => reject(new Error(`Erro na execução da query: ${err}`))
71
+ );
72
+ });
73
+ }
74
+
75
+ /**
76
+ * Executa uma consulta paginada.
77
+ * @param {string} query A query base.
78
+ * @param {Object} params Parâmetros da query.
79
+ * @param {number} limit Registros por página.
80
+ * @param {number} offset Ponto de início (pular x registros).
81
+ * @returns {Promise<Object>} Objeto contendo dados e metadados de paginação.
82
+ */
83
+ static async fetchPaginated(query, params = null, limit, offset) {
84
+ const safeLimit = Number(limit);
85
+ const safeOffset = Number(offset);
86
+
87
+ if (isNaN(safeLimit) || isNaN(safeOffset) || safeLimit < 0 || safeOffset < 0) {
88
+ throw new Error("Parâmetros 'limit' e 'offset' devem ser números positivos válidos.");
89
+ }
90
+
91
+ const paginatedQuery = `${query} LIMIT ${safeLimit} OFFSET ${safeOffset}`;
92
+
93
+ const data = await DataQueryService.fetch(paginatedQuery, params);
94
+
95
+ const currentPage = Math.floor(safeOffset / safeLimit) + 1;
96
+
97
+ return {
98
+ data,
99
+ meta: {
100
+ currentPage,
101
+ itemsPerPage: safeLimit,
102
+ totalItems: null,
103
+ totalPages: null
104
+ }
105
+ };
106
+ }
107
+ }
108
+
109
+ /**
110
+ * FormatUtils
111
+ * Utilitários para formatação de moeda, números e datas (Locale pt-BR).
112
+ */
113
+ class FormatUtils {
114
+ /**
115
+ * Formata valor para moeda BRL (R$).
116
+ * @param {number|string} value
117
+ * @returns {string} Ex: "R$ 1.250,00"
118
+ */
119
+ static currency(value) {
120
+ const numberVal = parseFloat(value);
121
+ if (isNaN(numberVal)) return "R$ 0,00";
122
+
123
+ return new Intl.NumberFormat('pt-BR', {
124
+ style: 'currency',
125
+ currency: 'BRL'
126
+ }).format(numberVal);
127
+ }
128
+
129
+ /**
130
+ * Formata número inteiro com separadores de milhar.
131
+ * @param {number|string} value
132
+ * @returns {string} Ex: "1.000"
133
+ */
134
+ static number(value) {
135
+ const numberVal = parseFloat(value);
136
+ if (isNaN(numberVal)) return "0";
137
+
138
+ return new Intl.NumberFormat('pt-BR', {
139
+ maximumFractionDigits: 0
140
+ }).format(numberVal);
141
+ }
142
+
143
+ /**
144
+ * Formata número decimal com 2 casas fixas.
145
+ * @param {number|string} value
146
+ * @returns {string} Ex: "1.200,50"
147
+ */
148
+ static decimal(value) {
149
+ const numberVal = parseFloat(value);
150
+ if (isNaN(numberVal)) return "0,00";
151
+
152
+ return new Intl.NumberFormat('pt-BR', {
153
+ minimumFractionDigits: 2,
154
+ maximumFractionDigits: 2
155
+ }).format(numberVal);
156
+ }
157
+
158
+ /**
159
+ * Retorna data atual no formato DD/MM/YYYY.
160
+ * @returns {string}
161
+ */
162
+ static currentDate() {
163
+ return new Intl.DateTimeFormat('pt-BR').format(new Date());
164
+ }
165
+
166
+ /**
167
+ * Converte string ISO (YYYY-MM-DD) para PT-BR (DD/MM/YYYY).
168
+ * @param {string} isoDateString
169
+ * @returns {string}
170
+ */
171
+ static formatDateToBR(isoDateString) {
172
+ if (!isoDateString) return '';
173
+ const parts = isoDateString.split('-');
174
+ if (parts.length !== 3) return isoDateString;
175
+
176
+ // [Ano, Mes, Dia] -> "Dia/Mes/Ano"
177
+ return `${parts[2]}/${parts[1]}/${parts[0]}`;
178
+ }
179
+
180
+ /**
181
+ * Retorna data atual em ISO (YYYY-MM-DD).
182
+ * Útil para inputs HTML tipo date.
183
+ * @returns {string}
184
+ */
185
+ static currentIsoDate() {
186
+ return new Date().toISOString().split('T')[0];
187
+ }
188
+
189
+ /**
190
+ * Data e Hora atuais (DD/MM/YYYY HH:mm).
191
+ * @returns {string}
192
+ */
193
+ static currentDateTime() {
194
+ const now = new Date();
195
+ const datePart = new Intl.DateTimeFormat('pt-BR').format(now);
196
+ const timePart = now.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' });
197
+ return `${datePart} ${timePart}`;
198
+ }
199
+ }
200
+
201
+ /**
202
+ * SortUtils
203
+ * Utilitários para ordenação de listas.
204
+ */
205
+ class SortUtils {
206
+ /**
207
+ * Ordena array de objetos.
208
+ * @param {Array<Object>} data Lista original.
209
+ * @param {string} key Chave do objeto para ordenar.
210
+ * @param {'asc'|'desc'} direction Direção da ordenação.
211
+ * @returns {Array<Object>} Nova lista ordenada.
212
+ */
213
+ static sortBy(data, key, direction = 'asc') {
214
+ if (!Array.isArray(data) || !key) return data;
215
+
216
+ const multiplier = direction === 'asc' ? 1 : -1;
217
+
218
+ return [...data].sort((a, b) => {
219
+ const valA = a[key];
220
+ const valB = b[key];
221
+
222
+ if (valA == null) return 1;
223
+ if (valB == null) return -1;
224
+
225
+ if (typeof valA === 'string' && typeof valB === 'string') {
226
+ return valA.localeCompare(valB) * multiplier;
227
+ }
228
+
229
+ if (valA < valB) return -1 * multiplier;
230
+ if (valA > valB) return 1 * multiplier;
231
+ return 0;
232
+ });
233
+ }
234
+ }
235
+
236
+ /**
237
+ * ExportService
238
+ * Exportação para Excel (.xlsx) e CSV.
239
+ */
240
+ class ExportService {
241
+ static CDN_URL = "https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js";
242
+
243
+ /**
244
+ * Carrega biblioteca externa (SheetJS) sob demanda.
245
+ * @private
246
+ */
247
+ static async loadDependency() {
248
+ if (typeof window !== 'undefined' && window.XLSX) return;
249
+
250
+ return new Promise((resolve, reject) => {
251
+ const script = document.createElement('script');
252
+ script.src = this.CDN_URL;
253
+ script.async = true;
254
+ script.onload = () => resolve();
255
+ script.onerror = () => reject(new Error('Falha ao baixar dependência XLSX.'));
256
+ document.head.appendChild(script);
257
+ });
258
+ }
259
+
260
+ /**
261
+ * Gera arquivo Excel.
262
+ * @param {Array<Object>} data Dados brutos.
263
+ * @param {Array<{key: string, display: string}>} columns Definição de colunas.
264
+ * @param {string} filename Nome do arquivo (sem extensão).
265
+ * @param {string} sheetName Nome da aba.
266
+ */
267
+ static async toExcel(data, columns, filename, sheetName = 'Dados') {
268
+ await this.loadDependency();
269
+
270
+ // Mapeia os dados para as chaves de exibição
271
+ const formattedData = data.map(item => {
272
+ const row = {};
273
+ columns.forEach(col => {
274
+ row[col.display] = item[col.key];
275
+ });
276
+ return row;
277
+ });
278
+
279
+ const worksheet = XLSX.utils.json_to_sheet(formattedData);
280
+ const workbook = XLSX.utils.book_new();
281
+ XLSX.utils.book_append_sheet(workbook, worksheet, sheetName);
282
+ XLSX.writeFile(workbook, `${filename}.xlsx`);
283
+ }
284
+
285
+ /**
286
+ * Gera arquivo CSV.
287
+ * @param {Array<Object>} data Dados brutos.
288
+ * @param {Array<{key: string, display: string}>} columns Definição de colunas.
289
+ * @param {string} filename Nome do arquivo (sem extensão).
290
+ */
291
+ static toCSV(data, columns, filename) {
292
+ const headerRow = columns.map(c => `"${c.display}"`).join(';');
293
+
294
+ const bodyRows = data.map(item => {
295
+ return columns.map(col => {
296
+ const val = item[col.key] ?? '';
297
+ const stringVal = String(val).replace(/"/g, '""').replace(/\r?\n|\r/g, ' ');
298
+ return `"${stringVal}"`;
299
+ }).join(';');
300
+ });
301
+
302
+ const csvContent = [headerRow, ...bodyRows].join('\r\n');
303
+
304
+ const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
305
+ const link = document.createElement('a');
306
+
307
+ if (link.download !== undefined) {
308
+ const url = URL.createObjectURL(blob);
309
+ link.setAttribute('href', url);
310
+ link.setAttribute('download', `${filename}.csv`);
311
+ link.style.visibility = 'hidden';
312
+ document.body.appendChild(link);
313
+ link.click();
314
+ document.body.removeChild(link);
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Método principal de exportação.
320
+ */
321
+ static async export(data, columns, filename = 'export', format = 'xlsx') {
322
+ if (!data?.length) throw new Error("Sem dados para exportar.");
323
+ if (!columns?.length) throw new Error("Colunas não definidas.");
324
+
325
+ const fmt = format.toLowerCase();
326
+
327
+ if (fmt === 'xlsx') {
328
+ await this.toExcel(data, columns, filename);
329
+ } else if (fmt === 'csv') {
330
+ this.toCSV(data, columns, filename);
331
+ } else {
332
+ throw new Error(`Formato '${format}' não suportado. Use 'xlsx' ou 'csv'.`);
333
+ }
334
+ }
335
+ }
336
+
337
+
338
+ export {
339
+ DataQueryService,
340
+ FormatUtils,
341
+ SortUtils,
342
+ ExportService,
343
+ DataQueryService as JSK,
344
+ FormatUtils as JFSK,
345
+ SortUtils as JOSK,
346
+ ExportService as JEXSK
347
+ };
package/package.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "@jtandrelevicius/utils-js-library",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "author": "Jeferson Tiago Alves Andrelevicius",
10
+ "license": "ISC"
11
+ }