"
`); win.document.close(); win.focus(); win.print(); }; const handlePrintID = () => { const node = printIDRef.current; if (!node) return; const html = node.outerHTML; const size = designID.orientation==="portrait" ? `${CARD.H}mm ${CARD.W}mm` : `${CARD.W}mm ${CARD.H}mm`; const win = window.open("", "_blank"); win.document.write(`ID Card${html}`); win.document.close(); win.focus(); win.print(); }; // Validate const [manualCode, setManualCode] = useState(""); const [scanResult, setScanResult] = useState(""); const validateById = (idOrSerial) => { const found = certs.find(c => c.id === idOrSerial || c.serial === idOrSerial); if (!found) return { ok:false }; return { ok:true, cert:found, trainee:getTrainee(found.traineeId), course:getCourse(found.courseId), trainer:getTrainer(found.trainerId) }; }; /* ---------------- Login UI ---------------- */ if (!session) { return (

Dayim Training Access

Login as Admin or Manager.

Username
Password
); } /* ---------------- UI ---------------- */ return (

Dayim Training Manager

Role: {session.role==="admin" ? "Admin" : "Training Manager"}

{/* Tabs */}
{["dashboard","trainees","trainers","courses","certificates","validate", ...(session.role==="admin" ? ["designer"] : [])].map(v => ( ))}
{/* Dashboard */} {tab==="dashboard" && (
{/* Quick Add Trainee (EN/AR) */}
{tErrors.name &&
{tErrors.name}
}
{tErrors.email &&
{tErrors.email}
}
{tErrors.id &&
{tErrors.id}
}
{tErrors.phone &&
{tErrors.phone}
}
{tErrors.photo &&
{tErrors.photo}
}
{/* Quick Add Trainer */}
{rErrors.name &&
{rErrors.name}
}
{rErrors.phone &&
{rErrors.phone}
}
{rErrors.email &&
{rErrors.email}
}
{/* Quick Add Course (Admin only) */} {session.role==="admin" && (
{cErrors.title &&
{cErrors.title}
}
{cErrors.code &&
{cErrors.code}
}
{ const f=e.target.files?.[0]; if(!f) return; const r=new FileReader(); r.onload=()=>setCIconPreview(String(r.result)); r.readAsDataURL(f); }}/> {cIconPreview && Icon preview}
)}
)} {/* Trainees */} {tab==="trainees" && (
{trainees.map(t=>( ))} {trainees.length===0 &&}
PhotoName (EN)الاسم (AR)EmailPhoneID NumberNotes
{t.photoDataUrl ? {t.nameEn||t.nameAr} :
}
{t.nameEn}{t.nameAr}{t.email}{t.phone}{t.idNumber}{t.notes}
No trainees yet. Add some from Dashboard.
)} {/* Trainers */} {tab==="trainers" && (
{trainers.map(t=>( ))} {trainers.length===0 &&}
PhotoName (EN)الاسم (AR)EmailPhoneNotes
{t.photoDataUrl ? {t.nameEn||t.nameAr} :
}
{t.nameEn}{t.nameAr}{t.email}{t.phone}{t.notes}
No trainers yet.
)} {/* Courses */} {tab==="courses" && (
{courses.map(c=>( ))} {courses.length===0 &&}
IconTitle (EN)العنوان (AR)CodeHoursDefault ExpiryDescription (EN)الوصف (AR)
{c.iconDataUrl ? {c.titleEn||c.titleAr} :
}
{c.titleEn}{c.titleAr}{c.code}{c.durationHours}{c.defaultExpiryMonths} mo{c.descriptionEn}{c.descriptionAr}
No courses yet.
)} {/* Certificates: Issue + List */} {tab==="courses" && null} {tab==="certificates" && (
Trainee
Trainer
Course
Issued On
setCertForm({...certForm, issuedOn:e.target.value})}/>
Expires On
setCertForm({...certForm, expiresOn:e.target.value})}/>
Verifier Hint (optional)
setCertForm({...certForm, verifierHint:e.target.value})} placeholder="e.g., Training Dept. contact"/>
{certs.map(c=>{ const t = getTrainee(c.traineeId); const tr= getTrainer(c.trainerId); const crs = getCourse(c.courseId); const expired = isPast(c.expiresOn); return ( ); })} {certs.length===0 &&}
SerialTraineeCourseTrainerIssuedExpiresStatus
{c.serial}{t?.nameEn || t?.nameAr} {crs?.iconDataUrl && Icon} {crs?.titleEn || crs?.titleAr} ({crs?.code}){tr?.nameEn || tr?.nameAr}{c.issuedOn}{c.expiresOn}{expired ? Expired : Valid} {session.role==="admin" && }
No certificates yet.
{/* Inline Previews */} {previewCert && (
Certificate (A4) Preview
ID Card Preview
)}
)} {/* Validate */} {tab==="validate" && (
Manual entry
setManualCode(e.target.value)}/>
Result
{(()=>{ if (!scanResult) return
No validation performed yet.
; let r; try { r = JSON.parse(scanResult); } catch { r = {ok:false}; } if (!r.ok) return
Not found. Check the serial/ID.
; const { cert, trainee, course, trainer } = r; const expired = isPast(cert.expiresOn); return (
{expired ? "Expired" : "Valid"} Serial: {cert.serial}
Trainee: {(trainee?.nameEn || trainee?.nameAr)}
Course: {(course?.titleEn || course?.titleAr)} ({course?.code})
Trainer: {(trainer?.nameEn || trainer?.nameAr) || "-"}
Issued: {cert.issuedOn}
Expires: {cert.expiresOn}
); })()}
)} {/* Designer (Admin only) */} {tab==="designer" && session.role==="admin" && (
{/* A4 CERT CONTROLS + PREVIEW */} } logo={logo} setLogo={(v)=>{ setLogo(v); setDesignA4(d=>({...d, logoDataUrl:v})); }} onTemplateUpload={(dataUrl, applyToBoth)=>{ setDesignA4(d=>({...d, bgTemplateDataUrl: dataUrl})); if (applyToBoth) setDesignID(d=>({...d, bgTemplateDataUrl: dataUrl})); }} /> {/* ID CARD CONTROLS + PREVIEW */} } logo={logo} setLogo={(v)=>{ setLogo(v); setDesignID(d=>({...d, logoDataUrl:v})); }} onTemplateUpload={(dataUrl, applyToBoth)=>{ setDesignID(d=>({...d, bgTemplateDataUrl: dataUrl})); if (applyToBoth) setDesignA4(d=>({...d, bgTemplateDataUrl: dataUrl})); }} />
)}
Dayim Training Manager
); } /* ---------------- Components ---------------- */ const Stat = ({ label, value, tone="default" }) => (
{label}
{value}
{tone==="warn"?"Expiring Soon":tone==="danger"?"Expired":"Active"}
); const Section = ({ title, desc, children }) => (
{title}
{desc &&
{desc}
}
{children}
); /* ---------- Designer Panel (Admin) ---------- */ function DesignerPanel({ kind, design, onChange, defaultDesign, preview, logo, setLogo, onTemplateUpload }) { const update = (path, value) => { const d = structuredClone(design); const parts = path.split("."); let ref = d; for (let i=0;i Math.max(min, Math.min(max, Number(v||0))); // local UI state for "apply to both" const [applyBoth, setApplyBoth] = useState(false); return (
{kind}
{preview ||
Issue a certificate to preview.
}
Orientation
Background
update("bgColor", e.target.value)}/>
Border
update("borderColor", e.target.value)}/>
Font Family
update("textFont", e.target.value)}/>
Watermark
Logo
{/* Upload Logo */}
Upload Logo
{ const f=e.target.files?.[0]; if(!f) return; const r=new FileReader(); r.onload=()=>{ setLogo(String(r.result)); }; r.readAsDataURL(f); }}/>
Default logo is preloaded. Upload to replace.
{/* NEW: Upload Template */}
Upload Template (image)
{ const f=e.target.files?.[0]; if(!f) return; const r=new FileReader(); r.onload=()=>{ const dataUrl=String(r.result); if(onTemplateUpload) onTemplateUpload(dataUrl, applyBoth); }; r.readAsDataURL(f); }} />
Fit
Opacity
update("templateOpacity", Number(e.target.value))} className="w-full"/>
{(design.templateOpacity ?? 1).toFixed(2)}
{design.bgTemplateDataUrl && (
Template preview
)}
{/* Positions (existing controls kept as-is) */}
{/* QR */}
QR X
update("qr.x", number(e.target.value,-999,999))}/>
QR Y
update("qr.y", number(e.target.value,-999,999))}/>
QR Size
update("qr.size", number(e.target.value,8,400))}/>
Logo X
update("logo.x", number(e.target.value,-999,999))}/>
Logo Y
update("logo.y", number(e.target.value,-999,999))}/>
Logo W
update("logo.w", number(e.target.value,1,999))}/>
Logo H
update("logo.h", number(e.target.value,1,999))}/>
{/* Photo (ID & A4 both support) */} {"photo" in design && <>
Photo?
Photo X
update("photo.x", number(e.target.value,-999,999))}/>
Photo Y
update("photo.y", number(e.target.value,-999,999))}/>
Photo W
update("photo.w", number(e.target.value,1,999))}/>
Photo H
update("photo.h", number(e.target.value,1,999))}/>
}
{/* Text fields (EN & AR + Notes) */}
{Object.keys(design.fields).map(key=>{ const f = design.fields[key]; return (
{key}
{"text" in f && update(`fields.${key}.text`, e.target.value)} placeholder="Text (supports placeholders)"/>}
X
update(`fields.${key}.x`, number(e.target.value,-999,999))}/>
Y
update(`fields.${key}.y`, number(e.target.value,-999,999))}/>
Size
update(`fields.${key}.size`, number(e.target.value,4,120))}/>
Color
update(`fields.${key}.color`, e.target.value)}/>
Weight
{"labelEn" in f &&
update(`fields.${key}.labelEn`, e.target.value)} placeholder="Label EN"/> update(`fields.${key}.labelAr`, e.target.value)} placeholder="التسمية AR"/>
}
); })}
Placeholders available in text fields: {"{nameEn}, {nameAr}, {courseEn}, {courseAr}, {trainerEn}, {trainerAr}, {issuedOn}, {expiresOn}, {serial}"}.
); } /* ---------- Certificate (A4) Renderer ---------- */ function CertificateA4({ design, cert, trainee, course, trainer, logo }) { if (!cert || !trainee || !course) return
Issue a certificate to preview.
; // helpers const boxStyle = { width: design.orientation==="portrait" ? "210mm" : "297mm", height:design.orientation==="portrait" ? "297mm" : "210mm", background: design.bgColor, border: `1mm solid ${design.borderColor}`, position:"relative", borderRadius:"3mm", fontFamily: design.textFont, overflow:"hidden", }; const payload = { id: cert.id, serial: cert.serial, trainee: trainee.nameEn || trainee.nameAr, course: course.code, expiresOn: cert.expiresOn }; const sub = (txt) => (txt || "") .replaceAll("{nameEn}", trainee.nameEn || "") .replaceAll("{nameAr}", trainee.nameAr || "") .replaceAll("{courseEn}", course.titleEn || "") .replaceAll("{courseAr}", course.titleAr || "") .replaceAll("{trainerEn}", (trainer?.nameEn || "")) .replaceAll("{trainerAr}", (trainer?.nameAr || "")) .replaceAll("{issuedOn}", cert.issuedOn) .replaceAll("{expiresOn}", cert.expiresOn) .replaceAll("{serial}", cert.serial); return (
{/* NEW: Template background */} {design.bgTemplateDataUrl && ( Template )} {/* Watermark */} {design.watermark && ( wm )} {/* Title EN */}
{design.fields.titleEn.text}
{/* Title AR */}
{design.fields.titleAr.text}
{/* Notes EN */}
{sub(design.fields.notesEn.text)}
{/* Notes AR */}
{sub(design.fields.notesAr.text)}
{/* Dates + Serial */}
Issued: {cert.issuedOn} • Expires: {cert.expiresOn}
Serial: {cert.serial}
{/* Trainee Photo (optional) */} {"photo" in design && design.photo.show && trainee.photoDataUrl && ( Photo )} {/* Logo */} {design.showLogo && (design.logoDataUrl || logo) && ( Logo )} {/* QR */}
{QRCodeCanvas ? :
QR
}
); } /* ---------- ID Card Renderer (CR80 in mm) ---------- */ function IDCard({ design, cert, trainee, course, trainer, logo }) { if (!cert || !trainee || !course) return
Issue a certificate to preview.
; const mm = (v)=> `${v}mm`; const boxStyle = { width: design.orientation==="portrait" ? `${CARD.H}mm` : `${CARD.W}mm`, height:design.orientation==="portrait" ? `${CARD.W}mm` : `${CARD.H}mm`, background: design.bgColor, border: `0.6mm solid ${design.borderColor}`, position:"relative", borderRadius:"2mm", fontFamily: design.textFont, overflow:"hidden", }; const payload = { id: cert.id, serial: cert.serial, trainee: trainee.nameEn || trainee.nameAr, course: course.code, expiresOn: cert.expiresOn }; const nameLine = (trainee.nameEn || "") + (trainee.nameAr ? ` / ${trainee.nameAr}` : ""); const courseLine = (course.titleEn || "") + (course.titleAr ? ` / ${course.titleAr}` : ""); return (
{/* NEW: Template background */} {design.bgTemplateDataUrl && ( Template )} {/* Watermark */} {design.watermark && ( wm )} {/* Titles */}
{design.fields.titleEn.text}
{design.fields.titleAr.text}
{/* Name */}
{design.fields.name.labelEn} {nameLine}
{/* Course */}
{design.fields.course.labelEn} {courseLine} ({course.code})
{/* Dates */}
Issued: {cert.issuedOn} • Exp: {cert.expiresOn}
{/* Trainee Photo */} {"photo" in design && design.photo.show && trainee.photoDataUrl && ( Photo )} {/* Logo */} {design.showLogo && (design.logoDataUrl || logo) && ( Logo )} {/* QR */}
{QRCodeCanvas ? :
QR
}
); } ReactDOM.createRoot(document.getElementById("dayim-training-app")).render(); "

Your list is empty, add products to the list to send a request

Return to Shop