/* ═══ 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 `
${icon('calendar', 16)}
`; } 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 = `
${months[month]} ${year}
Su
Mo
Tu
We
Th
Fr
Sa
`; 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(); }