Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f0ef20c41f | |||
| c252573824 |
+1
-1
@@ -273,7 +273,7 @@ body {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
display: block;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.front-photo-frame .placeholder-svg {
|
.front-photo-frame .placeholder-svg {
|
||||||
|
|||||||
+8
-74
@@ -46,12 +46,8 @@ export default function IdCardGenerator() {
|
|||||||
const [employeeId, setEmployeeId] = useState('');
|
const [employeeId, setEmployeeId] = useState('');
|
||||||
const [photoSrc, setPhotoSrc] = useState('');
|
const [photoSrc, setPhotoSrc] = useState('');
|
||||||
const [photoFile, setPhotoFile] = useState(null);
|
const [photoFile, setPhotoFile] = useState(null);
|
||||||
const [photoProcessing, setPhotoProcessing] = useState(false);
|
|
||||||
const [downloadStatus, setDownloadStatus] = useState({ processing: false, text: '⬇ Descargar' });
|
const [downloadStatus, setDownloadStatus] = useState({ processing: false, text: '⬇ Descargar' });
|
||||||
|
|
||||||
// Bloquea el botón Descargar hasta que nombre, puesto e ID estén llenos
|
|
||||||
const isFormComplete = name.trim() && role.trim() && employeeId.trim();
|
|
||||||
|
|
||||||
// --- Estados Lote / Excel Masivo ───
|
// --- Estados Lote / Excel Masivo ───
|
||||||
const [bulkEmployees, setBulkEmployees] = useState([]);
|
const [bulkEmployees, setBulkEmployees] = useState([]);
|
||||||
const [bulkStatusText, setBulkStatusText] = useState('');
|
const [bulkStatusText, setBulkStatusText] = useState('');
|
||||||
@@ -72,65 +68,14 @@ export default function IdCardGenerator() {
|
|||||||
}
|
}
|
||||||
}, [employeeId]);
|
}, [employeeId]);
|
||||||
|
|
||||||
const handlePhotoUpload = async (e) => {
|
const handlePhotoUpload = (e) => {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if (!file) return;
|
if (file) {
|
||||||
|
|
||||||
setPhotoFile(file);
|
setPhotoFile(file);
|
||||||
setPhotoSrc(URL.createObjectURL(file));
|
setPhotoSrc(URL.createObjectURL(file));
|
||||||
setPhotoProcessing(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const editedBlob = await processPhotoWithAI(file);
|
|
||||||
const editedUrl = URL.createObjectURL(editedBlob);
|
|
||||||
setPhotoSrc(editedUrl);
|
|
||||||
setPhotoFile(new File([editedBlob], file.name, { type: editedBlob.type || 'image/jpeg' }));
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error al procesar la foto con IA en n8n:', error);
|
|
||||||
alert('No se pudo procesar la foto con IA. Se usará la foto original para la vista previa.');
|
|
||||||
} finally {
|
|
||||||
setPhotoProcessing(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const processPhotoWithAI = async (file) => {
|
|
||||||
const N8N_WEBHOOK_URL = import.meta.env.VITE_N8N_WEBHOOK_URL;
|
|
||||||
const TOKEN_SECRETO = import.meta.env.VITE_WEBHOOK_TOKEN;
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('edited', file, file.name);
|
|
||||||
formData.append('name', name);
|
|
||||||
formData.append('role', role);
|
|
||||||
formData.append('employeeId', employeeId);
|
|
||||||
formData.append('fechaProcesado', new Date().toISOString());
|
|
||||||
|
|
||||||
const response = await fetch(N8N_WEBHOOK_URL, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Authorization': TOKEN_SECRETO
|
|
||||||
},
|
|
||||||
body: formData
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`El servidor de n8n rechazó la petición (Código: ${response.status})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const contentType = response.headers.get('content-type') || '';
|
|
||||||
if (contentType.includes('application/json')) {
|
|
||||||
const data = await response.json();
|
|
||||||
const bin = data.edited || data.data || data.foto_processed_base64 || data.photo || data.binary || (Array.isArray(data) && (data[0]?.edited || data[0]?.data));
|
|
||||||
const blobUrl = convertBinaryToBlobUrl(bin);
|
|
||||||
if (!blobUrl) throw new Error('n8n no devolvió un binario válido.');
|
|
||||||
const blob = await (await fetch(blobUrl)).blob();
|
|
||||||
return blob;
|
|
||||||
}
|
|
||||||
|
|
||||||
const blob = await response.blob();
|
|
||||||
if (!blob || blob.size === 0) throw new Error('n8n devolvió un binario vacío.');
|
|
||||||
return blob;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEmployeeIdChange = (e) => {
|
const handleEmployeeIdChange = (e) => {
|
||||||
const rawValue = e.target.value;
|
const rawValue = e.target.value;
|
||||||
const cleanValue = rawValue.replace(/[-\s]/g, '');
|
const cleanValue = rawValue.replace(/[-\s]/g, '');
|
||||||
@@ -272,7 +217,7 @@ export default function IdCardGenerator() {
|
|||||||
if (bulkEmployees.length === 0) return;
|
if (bulkEmployees.length === 0) return;
|
||||||
|
|
||||||
setBulkDownloadStatus({ processing: true, text: '🔄 Descargando...' });
|
setBulkDownloadStatus({ processing: true, text: '🔄 Descargando...' });
|
||||||
setBulkStatusText('Descargando retratos...');
|
setBulkStatusText('Conectando con n8n y descargando retratos optimizados por IA...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resultadosN8n = await enviarDatosAn8n(bulkEmployees);
|
const resultadosN8n = await enviarDatosAn8n(bulkEmployees);
|
||||||
@@ -308,7 +253,7 @@ export default function IdCardGenerator() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
setBulkStatusText('🎉 ¡Lote de carnets guardado correctamente!');
|
setBulkStatusText('🎉 ¡Lote de carnets guardado correctamente y registrado en Supabase!');
|
||||||
setBulkEmployees([]);
|
setBulkEmployees([]);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -449,8 +394,8 @@ export default function IdCardGenerator() {
|
|||||||
<label>Puesto</label>
|
<label>Puesto</label>
|
||||||
<input type="text" placeholder="Ej: Mercaderista" value={role} onChange={(e) => setRole(e.target.value)} />
|
<input type="text" placeholder="Ej: Mercaderista" value={role} onChange={(e) => setRole(e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
<div className="upload-btn" onClick={() => !photoProcessing && fileInputRef.current.click()} style={{ opacity: photoProcessing ? 0.6 : 1, cursor: photoProcessing ? 'wait' : 'pointer' }}>
|
<div className="upload-btn" onClick={() => fileInputRef.current.click()}>
|
||||||
{photoProcessing ? '⏳ Procesando foto con IA...' : 'Subir foto del colaborador'}
|
Subir foto del colaborador
|
||||||
</div>
|
</div>
|
||||||
<input type="file" accept="image/*" style={{ display: 'none' }} ref={fileInputRef} onChange={handlePhotoUpload} />
|
<input type="file" accept="image/*" style={{ display: 'none' }} ref={fileInputRef} onChange={handlePhotoUpload} />
|
||||||
</div>
|
</div>
|
||||||
@@ -502,7 +447,7 @@ export default function IdCardGenerator() {
|
|||||||
|
|
||||||
<div className="panel-section">
|
<div className="panel-section">
|
||||||
<div className="panel-section-title">Exportar</div>
|
<div className="panel-section-title">Exportar</div>
|
||||||
<button className="export-btn blue" disabled={downloadStatus.processing || !isFormComplete} onClick={triggerDownload} style={{ opacity: (!isFormComplete || downloadStatus.processing) ? 0.6 : 1 }}>
|
<button className="export-btn blue" disabled={downloadStatus.processing} onClick={triggerDownload}>
|
||||||
{downloadStatus.text}
|
{downloadStatus.text}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -516,18 +461,7 @@ export default function IdCardGenerator() {
|
|||||||
<div className="front-photo-frame" style={{ position: 'relative', overflow: 'hidden' }}>
|
<div className="front-photo-frame" style={{ position: 'relative', overflow: 'hidden' }}>
|
||||||
{photoSrc ? (
|
{photoSrc ? (
|
||||||
<img src={photoSrc} alt="Foto Colaborador" style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
|
<img src={photoSrc} alt="Foto Colaborador" style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
|
||||||
) : (null)}
|
) : (
|
||||||
{photoProcessing && (
|
|
||||||
<div style={{
|
|
||||||
position: 'absolute', inset: 0, display: 'flex',
|
|
||||||
alignItems: 'center', justifyContent: 'center',
|
|
||||||
background: 'rgba(0,0,0,0.45)', color: '#fff',
|
|
||||||
fontSize: '13px', fontWeight: '600', textAlign: 'center', padding: '8px'
|
|
||||||
}}>
|
|
||||||
⏳ Editando foto con IA...
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{!photoSrc && (
|
|
||||||
<svg className="placeholder-svg" viewBox="0 0 28 28" fill="none" style={{ width: '60%', height: '60%', opacity: 0.4 }}>
|
<svg className="placeholder-svg" viewBox="0 0 28 28" fill="none" style={{ width: '60%', height: '60%', opacity: 0.4 }}>
|
||||||
<circle cx="14" cy="10" r="5.5" stroke="currentColor" strokeWidth="1.5" />
|
<circle cx="14" cy="10" r="5.5" stroke="currentColor" strokeWidth="1.5" />
|
||||||
<path d="M3 24c0-6 22-6 22 0" stroke="currentColor" strokeWidth="1.5" />
|
<path d="M3 24c0-6 22-6 22 0" stroke="currentColor" strokeWidth="1.5" />
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ export async function downloadIDCards(config) {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
if (photoSrc) {
|
if (photoSrc) {
|
||||||
photoTagHtml = `<div style="width:100%; height:100%; background-image:url('${photoSrc}'); background-size:cover; background-position:center; background-repeat:no-repeat;"></div>`;
|
photoTagHtml = `<img src="${photoSrc}" crossorigin="anonymous" style="width:100%; height:100%; object-fit:cover; display:block;" />`;
|
||||||
}
|
}
|
||||||
|
|
||||||
vFront.innerHTML = `
|
vFront.innerHTML = `
|
||||||
@@ -356,7 +356,7 @@ export async function downloadBulkIDCards({ employees, baseUrl, onStateChange, o
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
if (validatedPhotoUrl) {
|
if (validatedPhotoUrl) {
|
||||||
photoTagHtml = `<div style="width:100%; height:100%; background-image:url('${validatedPhotoUrl}'); background-size:cover; background-position:center; background-repeat:no-repeat;"></div>`;
|
photoTagHtml = `<img src="${validatedPhotoUrl}" crossorigin="anonymous" style="width:100%; height:100%; object-fit:cover; display:block;" />`;
|
||||||
}
|
}
|
||||||
|
|
||||||
vFront.innerHTML = `
|
vFront.innerHTML = `
|
||||||
|
|||||||
Reference in New Issue
Block a user