From cb0636c9577282ba0b81c12bb45dee862f55dd5f Mon Sep 17 00:00:00 2001 From: EidanThen Date: Mon, 6 Jul 2026 22:05:31 -0400 Subject: [PATCH] feat: allow duplicated employee images in Supabase storage --- src/Generator.jsx | 16 ++++++++---- src/script/script.jsx | 2 +- src/services/storage.js | 54 +++++++++++++++++++++++++++++++++++------ 3 files changed, 58 insertions(+), 14 deletions(-) diff --git a/src/Generator.jsx b/src/Generator.jsx index b0647af..7a1f14e 100644 --- a/src/Generator.jsx +++ b/src/Generator.jsx @@ -1,6 +1,6 @@ import { useState, useRef, useEffect } from 'react'; import './Generator.css'; -import { renderQRCode, downloadIDCards, downloadBulkIDCards, getXLSXLib } from './script/script.jsx'; +import { renderQRCode, downloadIDCards, downloadBulkIDCards, getXLSXLib, clientTemplates } from './script/script.jsx'; import { uploadEmployeePhoto } from './services/storage.js'; import { supabase } from "./services/supabase"; @@ -148,11 +148,11 @@ export default function IdCardGenerator() { }; // --- ALMACENAMIENTO AUTOMÁTICO EN EL STORAGE E INSTANCIACIÓN RPC --- - const handleBulkSupabasePhotoSync = async (cedula, blobData) => { + const handleBulkSupabasePhotoSync = async (cedula, blobData, clientKey = '') => { const cleanCedula = String(cedula).replace(/[-\s]/g, ''); try { const customFile = new File([blobData], `${cleanCedula}.jpg`, { type: 'image/jpeg' }); - const remoteStorageUrl = await uploadEmployeePhoto(cleanCedula, customFile); + const remoteStorageUrl = await uploadEmployeePhoto(cleanCedula, customFile, clientKey); if (remoteStorageUrl) { const { data, error } = await supabase.rpc("save_employee_photo", { @@ -354,7 +354,7 @@ export default function IdCardGenerator() { if (!validateForm()) return; try { if (photoFile) { - const photoUrl = await uploadEmployeePhoto(employeeId, photoFile); + const photoUrl = await uploadEmployeePhoto(employeeId, photoFile, selectedClient); await supabase.rpc("save_employee_photo", { p_employee_number: employeeId, p_photo_url: photoUrl @@ -557,7 +557,13 @@ export default function IdCardGenerator() {
Reverso
-
+
diff --git a/src/script/script.jsx b/src/script/script.jsx index 983cda1..112416a 100644 --- a/src/script/script.jsx +++ b/src/script/script.jsx @@ -330,7 +330,7 @@ export async function downloadBulkIDCards({ employees, baseUrl, onStateChange, o if (blobData) { validatedPhotoUrl = URL.createObjectURL(blobData); if (onProcessEmployeePhoto) { - await onProcessEmployeePhoto(strictCleanId, blobData); + await onProcessEmployeePhoto(strictCleanId, blobData, emp.selectedClient || ''); } } } catch (err) { diff --git a/src/services/storage.js b/src/services/storage.js index d6e59c3..15e455f 100644 --- a/src/services/storage.js +++ b/src/services/storage.js @@ -1,21 +1,59 @@ import { supabase } from "./supabase"; -export async function uploadEmployeePhoto(employeeNumber, file) { +/** + * Sube la foto de un empleado al bucket "foto_empleados". + * + * El nombre del archivo sigue el patrón: + * _.jpg — primera foto + * _1.jpg — segunda foto del mismo empleado+cliente + * _2.jpg — tercera, etc. + * + * @param {string} employeeNumber - Número de cédula limpio (sin guiones/espacios) + * @param {File} file - Archivo de imagen a subir + * @param {string} [clientKey=''] - Nombre del cliente/proyecto (p.ej. "claro", "nestle") + * @returns {Promise} - URL pública del archivo subido + */ +export async function uploadEmployeePhoto(employeeNumber, file, clientKey = '') { + const extension = file.name.split('.').pop() || 'jpg'; - const extension = file.name.split(".").pop(); + // Normalizar el nombre del cliente: minúsculas, sin espacios ni caracteres especiales + const safeClient = clientKey + ? '_' + clientKey.toLowerCase().replace(/[^a-z0-9]/g, '') + : ''; - const fileName = `${employeeNumber}.${extension}`; + const baseName = `${employeeNumber}${safeClient}`; + + // Buscar archivos existentes con el mismo prefijo para elegir el sufijo correcto + const { data: existingFiles } = await supabase.storage + .from('foto_empleados') + .list('', { search: baseName }); + + // Filtrar exactamente los archivos que tengan el mismo baseName base + const pattern = new RegExp(`^${baseName}(\\d*)\\.${extension}$`); + const matches = (existingFiles || []).filter(f => pattern.test(f.name)); + + let fileName; + if (matches.length === 0) { + // No existe ninguno: usar el nombre base sin sufijo numérico + fileName = `${baseName}.${extension}`; + } else { + // Extraer los sufijos numéricos usados y elegir el siguiente + const usedSuffixes = matches.map(f => { + const m = f.name.match(pattern); + return m ? parseInt(m[1] || '0', 10) : 0; + }); + const nextSuffix = Math.max(...usedSuffixes) + 1; + fileName = `${baseName}${nextSuffix}.${extension}`; + } const { error } = await supabase.storage - .from("foto_empleados") - .upload(fileName, file, { - upsert: false - }); + .from('foto_empleados') + .upload(fileName, file, { upsert: false }); if (error) throw error; const { data } = supabase.storage - .from("foto_empleados") + .from('foto_empleados') .getPublicUrl(fileName); return data.publicUrl;