feat: allow duplicated employee images in Supabase storage #3
+11
-5
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useRef, useEffect } from 'react';
|
import { useState, useRef, useEffect } from 'react';
|
||||||
import './Generator.css';
|
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 { uploadEmployeePhoto } from './services/storage.js';
|
||||||
import { supabase } from "./services/supabase";
|
import { supabase } from "./services/supabase";
|
||||||
|
|
||||||
@@ -148,11 +148,11 @@ export default function IdCardGenerator() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// --- ALMACENAMIENTO AUTOMÁTICO EN EL STORAGE E INSTANCIACIÓN RPC ---
|
// --- 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, '');
|
const cleanCedula = String(cedula).replace(/[-\s]/g, '');
|
||||||
try {
|
try {
|
||||||
const customFile = new File([blobData], `${cleanCedula}.jpg`, { type: 'image/jpeg' });
|
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) {
|
if (remoteStorageUrl) {
|
||||||
const { data, error } = await supabase.rpc("save_employee_photo", {
|
const { data, error } = await supabase.rpc("save_employee_photo", {
|
||||||
@@ -354,7 +354,7 @@ export default function IdCardGenerator() {
|
|||||||
if (!validateForm()) return;
|
if (!validateForm()) return;
|
||||||
try {
|
try {
|
||||||
if (photoFile) {
|
if (photoFile) {
|
||||||
const photoUrl = await uploadEmployeePhoto(employeeId, photoFile);
|
const photoUrl = await uploadEmployeePhoto(employeeId, photoFile, selectedClient);
|
||||||
await supabase.rpc("save_employee_photo", {
|
await supabase.rpc("save_employee_photo", {
|
||||||
p_employee_number: employeeId,
|
p_employee_number: employeeId,
|
||||||
p_photo_url: photoUrl
|
p_photo_url: photoUrl
|
||||||
@@ -557,7 +557,13 @@ export default function IdCardGenerator() {
|
|||||||
|
|
||||||
<div className="card-wrap">
|
<div className="card-wrap">
|
||||||
<div className="card-label">Reverso</div>
|
<div className="card-label">Reverso</div>
|
||||||
<div className="card" id="cardBack" style={{ backgroundImage: `url('${baseUrl}${selectedClient === 'Colgate' ? 'images/AF Colgate' : 'images/AF ' + selectedClient} ${language}.png')` }}>
|
<div
|
||||||
|
className="card"
|
||||||
|
id="cardBack"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url('${baseUrl}${(clientTemplates[language] || clientTemplates['Esp'])[selectedClient] || (clientTemplates[language] || clientTemplates['Esp'])['Generico']}')`
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div className="back-qr-zone">
|
<div className="back-qr-zone">
|
||||||
<canvas ref={qrCanvasRef} width={55} height={55}></canvas>
|
<canvas ref={qrCanvasRef} width={55} height={55}></canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -330,7 +330,7 @@ export async function downloadBulkIDCards({ employees, baseUrl, onStateChange, o
|
|||||||
if (blobData) {
|
if (blobData) {
|
||||||
validatedPhotoUrl = URL.createObjectURL(blobData);
|
validatedPhotoUrl = URL.createObjectURL(blobData);
|
||||||
if (onProcessEmployeePhoto) {
|
if (onProcessEmployeePhoto) {
|
||||||
await onProcessEmployeePhoto(strictCleanId, blobData);
|
await onProcessEmployeePhoto(strictCleanId, blobData, emp.selectedClient || '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
+46
-8
@@ -1,21 +1,59 @@
|
|||||||
import { supabase } from "./supabase";
|
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:
|
||||||
|
* <cedula>_<cliente>.jpg — primera foto
|
||||||
|
* <cedula>_<cliente>1.jpg — segunda foto del mismo empleado+cliente
|
||||||
|
* <cedula>_<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<string>} - 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
|
const { error } = await supabase.storage
|
||||||
.from("foto_empleados")
|
.from('foto_empleados')
|
||||||
.upload(fileName, file, {
|
.upload(fileName, file, { upsert: false });
|
||||||
upsert: false
|
|
||||||
});
|
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|
||||||
const { data } = supabase.storage
|
const { data } = supabase.storage
|
||||||
.from("foto_empleados")
|
.from('foto_empleados')
|
||||||
.getPublicUrl(fileName);
|
.getPublicUrl(fileName);
|
||||||
|
|
||||||
return data.publicUrl;
|
return data.publicUrl;
|
||||||
|
|||||||
Reference in New Issue
Block a user