tailjng 0.1.3 → 0.1.5

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.
@@ -9,7 +9,6 @@ import { HttpParams } from '@angular/common/http';
9
9
  import { Link2Off, Link, Clipboard, CircleAlert, Clock, Calendar, EllipsisVertical, Trash, Edit, Pencil, PencilLine, ListRestart, FileUp, Download, MonitorUp, FileSpreadsheet, Cpu, Trash2, Eraser, ArrowDownWideNarrow, Filter, ArrowBigRight, ChevronsRight, ChevronRight, ChevronLeft, ChevronsLeft, ListFilter, Table, Loader2, Moon, Sun, Save, Copy, Search, SquareDashedMousePointer, ChevronsUpDown, ChevronDown, ChevronUp, Eye, Upload, ImageOff, Images, Image, Minimize2, Scan, RefreshCcw, RotateCcw, RotateCw, ZoomOut, ZoomIn, Check, X, CircleHelp, TriangleAlert, CircleX, CircleCheck, Info } from 'lucide-angular';
10
10
  import * as FileSaver from 'file-saver';
11
11
  import * as ExcelJS from 'exceljs';
12
- import * as XLSX from 'xlsx';
13
12
 
14
13
  const TAILJNG_CONFIG = new InjectionToken('TAILJNG_CONFIG');
15
14
  // providers: [
@@ -2097,18 +2096,33 @@ class JUploadFilterService {
2097
2096
  setLoadingFn(true);
2098
2097
  const reader = new FileReader();
2099
2098
  reader.onload = (e) => {
2100
- const data = new Uint8Array(e.target.result);
2101
- const workbook = XLSX.read(data, { type: 'array' });
2102
- const sheet = workbook.Sheets[workbook.SheetNames[0]];
2103
- const jsonRaw = XLSX.utils.sheet_to_json(sheet, { header: 1, defval: 'S/N' });
2099
+ void this.parseUploadedWorkbook(e, columns, button, afterProcessing, setLoadingFn, clearFileInputFn);
2100
+ };
2101
+ reader.readAsArrayBuffer(file);
2102
+ }
2103
+ async parseUploadedWorkbook(e, columns, button, afterProcessing, setLoadingFn, clearFileInputFn) {
2104
+ try {
2105
+ const buffer = e.target?.result;
2106
+ if (!(buffer instanceof ArrayBuffer)) {
2107
+ throw new Error('No se pudo leer el archivo Excel.');
2108
+ }
2109
+ const jsonRaw = await this.readWorkbookAsRowArrays(buffer);
2104
2110
  const headers = jsonRaw[1];
2105
2111
  const rows = jsonRaw.slice(2);
2106
- // Validate headers
2112
+ if (!headers?.length) {
2113
+ this.alertToastService.AlertToast({
2114
+ type: 'error',
2115
+ title: 'Archivo inválido',
2116
+ description: 'El archivo Excel no contiene encabezados en la fila esperada.',
2117
+ });
2118
+ clearFileInputFn();
2119
+ setLoadingFn(false);
2120
+ return;
2121
+ }
2107
2122
  if (!this.validateHeaders(headers, columns, button.ignoreKeyUpload ?? [], setLoadingFn)) {
2108
2123
  clearFileInputFn();
2109
2124
  return;
2110
2125
  }
2111
- // Get the header key map
2112
2126
  const mapHeader = this.getHeaderKeyMap(columns, button.ignoreKeyUpload ?? []);
2113
2127
  const json = rows.map(row => {
2114
2128
  const obj = {};
@@ -2120,7 +2134,6 @@ class JUploadFilterService {
2120
2134
  if (columnConfig) {
2121
2135
  value = this.converterService.parseData(value, columnConfig);
2122
2136
  }
2123
- // Force to string if it's an identification field
2124
2137
  if (key?.toLowerCase().includes('identification_')) {
2125
2138
  if (typeof value === 'number') {
2126
2139
  value = value.toString();
@@ -2131,71 +2144,51 @@ class JUploadFilterService {
2131
2144
  });
2132
2145
  return obj;
2133
2146
  });
2134
- // Process selected IDs
2135
2147
  forkJoin(this.getIdsSelectedData(button)).subscribe({
2136
2148
  next: (results) => {
2137
2149
  let hasMissing = false;
2138
2150
  const missingMessages = [];
2139
2151
  results.forEach((result) => {
2140
- const { key, data, mappingInfo } = result;
2152
+ const { data, mappingInfo } = result;
2141
2153
  const keyReturn = mappingInfo.keyReturn;
2142
2154
  const keyAlternate = mappingInfo.keyAlternate;
2143
2155
  const keyColumnSearch = mappingInfo.keyColumnSearch;
2144
2156
  const isOptional = mappingInfo.optional ?? false;
2145
2157
  if (!keyReturn || !keyColumnSearch)
2146
2158
  return;
2147
- // Get the column label to display in errors
2148
2159
  const columnLabel = columns.find(col => col.key === keyColumnSearch)?.label
2149
2160
  ?? keyColumnSearch;
2150
2161
  const missingValues = [];
2151
- // Iterate over each row of the JSON loaded from Excel
2152
2162
  json.forEach(item => {
2153
2163
  const currentValue = item[keyColumnSearch];
2154
2164
  if (currentValue !== undefined && currentValue !== null) {
2155
- // For example, from keyColumnSearch = 'status.name_status'
2156
- // you get fieldName = 'name_status'
2157
2165
  const fieldName = keyColumnSearch.split('.').pop();
2158
- // Search for the value in the catalog obtained from the API
2159
2166
  const found = data.find((apiItem) => {
2160
2167
  return apiItem[fieldName] === currentValue;
2161
2168
  });
2162
2169
  if (found) {
2163
- // If it finds the value in the catalog:
2164
- // - delete the textual column (e.g. 'status.name_status')
2165
- // - and leave the id (e.g. id_status) in the object
2166
- // delete item[keyColumnSearch];
2167
- // If mappingInfo brings keyAlternate, use that field, if not, use keyReturn
2168
2170
  const valueToAssign = found[keyAlternate ?? keyReturn];
2169
2171
  item[keyReturn] = valueToAssign;
2170
2172
  }
2171
- else {
2172
- if (isOptional) {
2173
- // If it is optional and does not exist, simply delete the field
2174
- delete item[keyColumnSearch];
2175
- item[keyReturn] = null;
2176
- }
2177
- else {
2178
- // If it is not optional and the value was not found, accumulate it to show an error
2179
- missingValues.push(currentValue);
2180
- }
2181
- }
2182
- }
2183
- else {
2184
- // If the value comes empty and is optional, delete the field and leave it as null
2185
- if (isOptional) {
2173
+ else if (isOptional) {
2186
2174
  delete item[keyColumnSearch];
2187
2175
  item[keyReturn] = null;
2188
2176
  }
2177
+ else {
2178
+ missingValues.push(currentValue);
2179
+ }
2180
+ }
2181
+ else if (isOptional) {
2182
+ delete item[keyColumnSearch];
2183
+ item[keyReturn] = null;
2189
2184
  }
2190
2185
  });
2191
- // If there were values that were not found in the catalog list
2192
2186
  if (missingValues.length > 0) {
2193
2187
  hasMissing = true;
2194
2188
  const uniqueMissing = Array.from(new Set(missingValues));
2195
2189
  missingMessages.push(`No se encontraron coincidencias en la columna [${columnLabel}] para los valores: ${uniqueMissing.join(', ')}`);
2196
2190
  }
2197
2191
  });
2198
- // If there were matching errors, show a warning message
2199
2192
  if (hasMissing) {
2200
2193
  this.alertToastService.AlertToast({
2201
2194
  type: 'warning',
@@ -2208,11 +2201,8 @@ class JUploadFilterService {
2208
2201
  return;
2209
2202
  }
2210
2203
  console.log('JSON final con IDs:', json);
2211
- // Determine columns present in the Excel file
2212
2204
  const fileHeadersUpper = (headers ?? []).map((h) => h?.trim().toUpperCase());
2213
- // Generate the mapping of header -> key
2214
2205
  this.getHeaderKeyMap(columns, button.ignoreKeyUpload ?? []);
2215
- // Filter columns that are in the Excel file
2216
2206
  const filteredColumns = columns.filter(col => {
2217
2207
  const header = col.label?.trim().toUpperCase();
2218
2208
  return (col.visible &&
@@ -2220,9 +2210,6 @@ class JUploadFilterService {
2220
2210
  fileHeadersUpper.includes(header) &&
2221
2211
  !button.ignoreKeyUpload.includes(col.key));
2222
2212
  });
2223
- // Object to store the equivalences
2224
- // keyReturn -> { keyColumnSearch: value }
2225
- // For example: { id_status: { name_status: 'Activo' } }
2226
2213
  const equivalences = {};
2227
2214
  results.forEach((result) => {
2228
2215
  const { data, mappingInfo } = result;
@@ -2236,7 +2223,6 @@ class JUploadFilterService {
2236
2223
  equivalences[keyReturn][item[idKey]] = item[keyColumnSearch.split('.').pop()];
2237
2224
  });
2238
2225
  });
2239
- // Execute the callback passing the json and the filtered columns
2240
2226
  afterProcessing(json, filteredColumns, equivalences);
2241
2227
  clearFileInputFn();
2242
2228
  },
@@ -2244,8 +2230,60 @@ class JUploadFilterService {
2244
2230
  setLoadingFn(false);
2245
2231
  }
2246
2232
  });
2247
- };
2248
- reader.readAsArrayBuffer(file);
2233
+ }
2234
+ catch {
2235
+ this.alertToastService.AlertToast({
2236
+ type: 'error',
2237
+ title: 'Error al leer Excel',
2238
+ description: 'No se pudo procesar el archivo. Verifica que sea un .xlsx válido.',
2239
+ });
2240
+ clearFileInputFn();
2241
+ setLoadingFn(false);
2242
+ }
2243
+ }
2244
+ async readWorkbookAsRowArrays(buffer) {
2245
+ const workbook = new ExcelJS.Workbook();
2246
+ await workbook.xlsx.load(buffer);
2247
+ const sheet = workbook.worksheets[0];
2248
+ if (!sheet) {
2249
+ return [];
2250
+ }
2251
+ const rowCount = sheet.rowCount;
2252
+ const colCount = sheet.columnCount;
2253
+ const result = [];
2254
+ for (let rowIndex = 1; rowIndex <= rowCount; rowIndex += 1) {
2255
+ const row = sheet.getRow(rowIndex);
2256
+ const rowData = [];
2257
+ for (let colIndex = 1; colIndex <= colCount; colIndex += 1) {
2258
+ rowData.push(this.getCellPlainValue(row.getCell(colIndex)));
2259
+ }
2260
+ result.push(rowData);
2261
+ }
2262
+ return result;
2263
+ }
2264
+ getCellPlainValue(cell) {
2265
+ const value = cell.value;
2266
+ if (value == null || value === '') {
2267
+ return 'S/N';
2268
+ }
2269
+ if (value instanceof Date) {
2270
+ return value;
2271
+ }
2272
+ if (typeof value === 'object') {
2273
+ if ('result' in value) {
2274
+ return value.result ?? 'S/N';
2275
+ }
2276
+ if ('richText' in value && Array.isArray(value.richText)) {
2277
+ return value.richText.map((part) => part.text).join('');
2278
+ }
2279
+ if ('text' in value && typeof value.text === 'string') {
2280
+ return value.text;
2281
+ }
2282
+ if ('hyperlink' in value && 'text' in value) {
2283
+ return value.text ?? 'S/N';
2284
+ }
2285
+ }
2286
+ return value;
2249
2287
  }
2250
2288
  /**
2251
2289
  * Generates catalogs of valid values for select-type fields.