Primer commit
This commit is contained in:
@@ -0,0 +1,344 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import './App.css';
|
||||
// Importamos las funciones lógicas nativas desde el archivo portado script.jsx
|
||||
import { renderQRCode, downloadIDCards } from './script/script.jsx';
|
||||
import { uploadEmployeePhoto } from './services/storage.js';
|
||||
import { supabase } from "./services/supabase";
|
||||
|
||||
export default function IdCardGenerator() {
|
||||
// --- Estados para manejar la lógica en tiempo real ───
|
||||
const [name, setName] = useState('');
|
||||
const [role, setRole] = useState('');
|
||||
const [language, setLanguage] = useState('Esp'); // 'Esp' o 'Ing'
|
||||
const [selectedClient, setSelectedClient] = useState('Generico'); // Sin tildes de forma interna
|
||||
const [employeeId, setEmployeeId] = useState('');
|
||||
const [photoSrc, setPhotoSrc] = useState('');
|
||||
const [photoFile, setPhotoFile] = useState(null);
|
||||
|
||||
// Estado para controlar la UI del botón de descarga mientras procesa
|
||||
const [downloadStatus, setDownloadStatus] = useState({ processing: false, text: '⬇ Descargar' });
|
||||
|
||||
// Referencias del DOM controladas por React
|
||||
const fileInputRef = useRef(null);
|
||||
const qrCanvasRef = useRef(null);
|
||||
|
||||
// EFECTO AUTOMÁTICO: Renderiza el QR en tiempo real mientras escribes mediante referencias
|
||||
useEffect(() => {
|
||||
if (qrCanvasRef.current) {
|
||||
renderQRCode(employeeId, qrCanvasRef.current);
|
||||
}
|
||||
}, [employeeId]);
|
||||
|
||||
const handlePhotoUpload = (e) => {
|
||||
const file = e.target.files[0];
|
||||
|
||||
if (file) {
|
||||
setPhotoFile(file);
|
||||
|
||||
const localUrl = URL.createObjectURL(file);
|
||||
setPhotoSrc(localUrl);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLanguageChange = (lang) => {
|
||||
setLanguage(lang);
|
||||
};
|
||||
|
||||
const handleClientChange = (e) => {
|
||||
setSelectedClient(e.target.value);
|
||||
};
|
||||
|
||||
// Forzar generación del QR manualmente si se desea mediante el botón verde
|
||||
const triggerGenerateQR = () => {
|
||||
if (qrCanvasRef.current) {
|
||||
renderQRCode(employeeId, qrCanvasRef.current);
|
||||
}
|
||||
};
|
||||
|
||||
// Disparador del motor de renderizado ULTRA MAX QUALITY en sandbox
|
||||
const triggerDownload = async () => {
|
||||
if (!validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
// Solo si el usuario seleccionó una foto
|
||||
if (photoFile) {
|
||||
|
||||
const photoUrl = await uploadEmployeePhoto(
|
||||
employeeId,
|
||||
photoFile
|
||||
);
|
||||
|
||||
console.log("Foto subida:", photoUrl);
|
||||
|
||||
const { data, error } = await supabase.rpc(
|
||||
"save_employee_photo",
|
||||
{
|
||||
p_employee_number: employeeId,
|
||||
p_photo_url: photoUrl
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!data.success) {
|
||||
console.warn(data.message);
|
||||
} else {
|
||||
console.log(data.message);
|
||||
}
|
||||
}
|
||||
|
||||
await downloadIDCards({
|
||||
name,
|
||||
role,
|
||||
selectedClient,
|
||||
language,
|
||||
photoSrc,
|
||||
baseUrl: import.meta.env.BASE_URL.endsWith('/')
|
||||
? import.meta.env.BASE_URL
|
||||
: `${import.meta.env.BASE_URL}/`,
|
||||
qrCanvas: qrCanvasRef.current,
|
||||
onStateChange: setDownloadStatus
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert(err.message);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const baseUrl = import.meta.env.BASE_URL.endsWith('/')
|
||||
? import.meta.env.BASE_URL
|
||||
: `${import.meta.env.BASE_URL}/`;
|
||||
|
||||
const validateForm = () => {
|
||||
if (!name.trim()) {
|
||||
alert("Debe ingresar el nombre.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!role.trim()) {
|
||||
alert("Debe ingresar el puesto.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!employeeId.trim()) {
|
||||
alert("Debe ingresar el ID del empleado.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!photoFile) {
|
||||
alert("Debe subir la foto del colaborador.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="page-title">
|
||||
<h1>GLM ID Card Generator</h1>
|
||||
<p>GomezLee Marketing · Carnet Corporativo CR80</p>
|
||||
</div>
|
||||
|
||||
<div className="main-container">
|
||||
<div className="panel">
|
||||
|
||||
<div className="preview-badge">
|
||||
<div className="dot"></div>
|
||||
<span>Vista previa en tiempo real</span>
|
||||
</div>
|
||||
|
||||
{/* Sección Colaborador */}
|
||||
<div className="panel-section">
|
||||
<div className="panel-section-title">Colaborador</div>
|
||||
|
||||
<div className="field">
|
||||
<label>Nombre</label>
|
||||
<input
|
||||
type="text"
|
||||
id="inputName"
|
||||
placeholder="Ej: Alexi Zabala"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<label>Puesto</label>
|
||||
<input
|
||||
type="text"
|
||||
id="inputRole"
|
||||
placeholder="Ej: Mercaderista"
|
||||
value={role}
|
||||
onChange={(e) => setRole(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="upload-btn" onClick={() => fileInputRef.current.click()}>
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
|
||||
<circle cx="7" cy="5" r="3" stroke="currentColor" strokeWidth="1.2" />
|
||||
<path d="M1 12c0-3.5 12-3.5 12 0" stroke="currentColor" strokeWidth="1.2" />
|
||||
</svg>
|
||||
Subir foto del colaborador
|
||||
</div>
|
||||
<input
|
||||
type="file"
|
||||
id="photoInput"
|
||||
accept="image/*"
|
||||
style={{ display: 'none' }}
|
||||
ref={fileInputRef}
|
||||
onChange={handlePhotoUpload}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="divider"></div>
|
||||
|
||||
{/* Sección Cliente / Proyecto */}
|
||||
<div className="panel-section">
|
||||
<div className="panel-section-title">Cliente / Proyecto</div>
|
||||
|
||||
<div className="lang-container">
|
||||
<button
|
||||
type="button"
|
||||
className={`lang-btn ${language === 'Esp' ? 'active' : ''}`}
|
||||
onClick={() => handleLanguageChange('Esp')}
|
||||
>
|
||||
Español
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`lang-btn ${language === 'Ing' ? 'active' : ''}`}
|
||||
onClick={() => handleLanguageChange('Ing')}
|
||||
>
|
||||
Inglés
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="field">
|
||||
<label>Seleccionar Cliente</label>
|
||||
<select id="selectClient" value={selectedClient} onChange={handleClientChange}>
|
||||
<option value="Generico">Genérico (Por defecto)</option>
|
||||
<option value="Claro">Claro</option>
|
||||
<option value="Colgate">Colgate</option>
|
||||
<option value="KitchenAid">KitchenAid</option>
|
||||
<option value="Kraft">Kraft</option>
|
||||
<option value="Motorola">Motorola</option>
|
||||
<option value="Nestle">Nestle</option>
|
||||
<option value="P&G">P&G</option>
|
||||
<option value="Philip Morris">Philip Morris International</option>
|
||||
<option value="Whirlpool">Whirlpool</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="divider"></div>
|
||||
|
||||
{/* Sección Código QR */}
|
||||
<div className="panel-section">
|
||||
<div className="panel-section-title">Código QR</div>
|
||||
<div className="field">
|
||||
<label>ID del empleado</label>
|
||||
<input
|
||||
type="text"
|
||||
id="inputQR"
|
||||
placeholder="Ej: 12345"
|
||||
value={employeeId}
|
||||
onChange={(e) => setEmployeeId(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className="export-btn"
|
||||
style={{ background: '#6CC24A', marginTop: '2px' }}
|
||||
onClick={triggerGenerateQR}
|
||||
>
|
||||
⟳ Generar QR
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="divider"></div>
|
||||
|
||||
{/* Sección Exportar */}
|
||||
<div className="panel-section">
|
||||
<div className="panel-section-title">Exportar</div>
|
||||
<button
|
||||
className="export-btn blue"
|
||||
id="btnDownload"
|
||||
disabled={downloadStatus.processing}
|
||||
onClick={triggerDownload}
|
||||
>
|
||||
{downloadStatus.text}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* --- STAGE: Vista previa de las tarjetas ─── */}
|
||||
<div className="stage">
|
||||
|
||||
{/* Frente */}
|
||||
<div className="card-wrap">
|
||||
<div className="card-label">Frente</div>
|
||||
<div
|
||||
className="card"
|
||||
id="cardFront"
|
||||
style={{ backgroundImage: `url('${baseUrl}images/AF GLM Frente.png')` }}
|
||||
>
|
||||
<div className="front-photo-frame" id="photoFrame" style={{ position: 'relative', overflow: 'hidden' }}>
|
||||
{photoSrc ? (
|
||||
<img
|
||||
id="photoImg"
|
||||
src={photoSrc}
|
||||
alt="Foto Colaborador"
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
display: 'block'
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<svg className="placeholder-svg" id="photoPlaceholder" 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" />
|
||||
<path d="M3 24c0-6 22-6 22 0" stroke="currentColor" strokeWidth="1.5" />
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="front-dynamic-name" id="displayName">
|
||||
{name ? name.toUpperCase() : 'NOMBRE APELLIDO'}
|
||||
</div>
|
||||
<div className="front-dynamic-role" id="displayRole">
|
||||
{role ? role.toUpperCase() : 'PUESTO'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Reverso */}
|
||||
<div className="card-wrap">
|
||||
<div className="card-label">Reverso</div>
|
||||
<div
|
||||
className="card"
|
||||
id="cardBack"
|
||||
style={{
|
||||
backgroundImage: `url('${baseUrl}${selectedClient === 'Colgate' ? 'images/GLM Colgate' : 'images/AF ' + selectedClient} ${language}.png')`
|
||||
}}
|
||||
>
|
||||
<div className="back-qr-zone">
|
||||
{/* Asignamos la referencia de React para un control de pixeles seguro */}
|
||||
<canvas ref={qrCanvasRef} width="55" height="55"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user