/* ═══ CYPHER-HR Design Module ═══ */
/* Icon management via thesvg CDN + SVG fallbacks */
const THESVG_CDN = 'https://cdn.jsdelivr.net/npm/@thesvg/icons/icons';
const iconCache = {};
const SVG_ICONS = {
dashboard: ``,
users: ``,
calendar: ``,
report: ``,
settings: ``,
logout: ``,
plus: ``,
check: ``,
x: ``,
clock: ``,
briefcase: ``,
building: ``,
shield: ``,
edit: ``,
trash: ``,
search: ``,
chevronRight: ``,
home: ``,
download: ``,
arrowUp: ``,
arrowDown: ``,
userPlus: ``,
fileText: ``,
checkCircle: ``,
mail: ``,
lock: ``,
moon: ``,
sun: ``,
};
function icon(name, size = 20) {
const defaultSvg = SVG_ICONS[name] || SVG_ICONS.dashboard;
if (iconCache[name] && iconCache[name] !== 'loading') {
return `${iconCache[name]}`;
}
if (!iconCache[name]) {
iconCache[name] = 'loading';
fetch(`/thesvg/${name}.js`)
.then(res => {
if (!res.ok) throw new Error('Not found');
return res.text();
})
.then(text => {
const match = text.match(/export const svg = \`(.*?)\`;/s) || text.match(/export const variants = \{[\s\S]*?"mono": \`(.*?)\`/s) || text.match(/export const variants = \{[\s\S]*?"default": \`(.*?)\`/s);
const finalSvg = (match && match[1]) ? match[1] : defaultSvg;
iconCache[name] = finalSvg;
document.querySelectorAll(`.thesvg-icon[data-icon="${name}"]`).forEach(el => {
el.innerHTML = finalSvg;
el.classList.remove('thesvg-icon');
});
})
.catch(err => {
iconCache[name] = defaultSvg;
});
}
return `${defaultSvg}`;
}
/* ═══ Design Utilities ═══ */
const Design = {
colors: ['#1570ef','#7a5af8','#ee46bc','#f04438','#12b76a','#f79009','#0ba5ec','#66c61c','#ef6820'],
randomColor() {
return this.colors[Math.floor(Math.random() * this.colors.length)];
},
initRipple() {
document.addEventListener('click', e => {
const btn = e.target.closest('.btn');
if (!btn) return;
const ripple = document.createElement('span');
const rect = btn.getBoundingClientRect();
ripple.style.cssText = `position:absolute;border-radius:50%;background:rgba(255,255,255,0.3);width:0;height:0;left:${e.clientX-rect.left}px;top:${e.clientY-rect.top}px;transform:translate(-50%,-50%);pointer-events:none;animation:ripple 0.4s ease-out forwards`;
btn.style.position = 'relative';
btn.style.overflow = 'hidden';
btn.appendChild(ripple);
setTimeout(() => ripple.remove(), 400);
});
if (!document.getElementById('ripple-style')) {
const s = document.createElement('style');
s.id = 'ripple-style';
s.textContent = '@keyframes ripple{to{width:200px;height:200px;opacity:0}}';
document.head.appendChild(s);
}
}
};
Design.initRipple();
/* ═══ Custom Date Picker (DD:MM:YYYY format) ═══ */
function dateInput(id, required = false, placeholder = 'DD:MM:YYYY') {
const req = required ? 'required' : '';
return `
`;
}
function formatDateInput(el) {
let v = el.value.replace(/[^\d]/g, '');
if (v.length > 2) v = v.slice(0,2) + ':' + v.slice(2);
if (v.length > 5) v = v.slice(0,5) + ':' + v.slice(5,9);
el.value = v;
}
function parseDateInput(id) {
const val = document.getElementById(id)?.value;
if (!val) return '';
const parts = val.split(':');
if (parts.length !== 3) return val;
return `${parts[2]}-${parts[1]}-${parts[0]}`;
}
let activeDatepicker = null;
function openDatepicker(id) {
if (activeDatepicker && activeDatepicker !== id) closeDatepicker();
activeDatepicker = id;
const popup = document.getElementById(`${id}_popup`);
if (!popup) return;
const val = document.getElementById(id).value;
let d = new Date();
if (val && val.length === 10) {
const parts = val.split(':');
d = new Date(parts[2], parseInt(parts[1]) - 1, parts[0]);
}
renderCalendar(id, d.getMonth(), d.getFullYear());
popup.classList.add('show');
}
function closeDatepicker() {
if (!activeDatepicker) return;
const popup = document.getElementById(`${activeDatepicker}_popup`);
if (popup) popup.classList.remove('show');
activeDatepicker = null;
}
document.addEventListener('click', (e) => {
if (activeDatepicker) {
const wrapper = document.getElementById(`${activeDatepicker}_wrapper`);
if (wrapper && !wrapper.contains(e.target)) {
closeDatepicker();
}
}
});
function renderCalendar(id, month, year) {
const popup = document.getElementById(`${id}_popup`);
if (!popup) return;
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
const daysInMonth = new Date(year, month + 1, 0).getDate();
const firstDay = new Date(year, month, 1).getDay();
let html = `
`;
for (let i = 0; i < firstDay; i++) {
html += `
`;
}
const today = new Date();
const isCurrentMonth = today.getMonth() === month && today.getFullYear() === year;
const currentVal = document.getElementById(id).value;
let selD = -1, selM = -1, selY = -1;
if (currentVal && currentVal.length === 10) {
const parts = currentVal.split(':');
selD = parseInt(parts[0]); selM = parseInt(parts[1]) - 1; selY = parseInt(parts[2]);
}
for (let i = 1; i <= daysInMonth; i++) {
let classes = 'datepicker-day';
if (isCurrentMonth && today.getDate() === i) classes += ' today';
if (selY === year && selM === month && selD === i) classes += ' selected';
html += `
${i}
`;
}
html += `
`;
popup.innerHTML = html;
// Fix nav buttons rotation
const navs = popup.querySelectorAll('.datepicker-nav');
if (navs[0]) navs[0].querySelector('svg').style.transform = 'rotate(180deg)';
}
function changeMonth(id, month, year) {
if (month < 0) { month = 11; year--; }
if (month > 11) { month = 0; year++; }
renderCalendar(id, month, year);
}
function selectDate(id, day, month, year) {
const dd = String(day).padStart(2, '0');
const mm = String(month + 1).padStart(2, '0');
const yyyy = year;
const input = document.getElementById(id);
input.value = `${dd}:${mm}:${yyyy}`;
// Trigger onchange manually if anything listens to it
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
closeDatepicker();
}