// assetCard.js — renders the asset card into #asset-card-container import CONFIG from '../config.js'; import { normalizeUsername, usernameFuzzyMatch, usernameFirstNameMatch, usernameNameInitialMatch } from './usernameUtils.js'; import { getCustomerContactNames } from './assetBrowser.js'; const { subdomain, baseUrl } = CONFIG.syncro; // ── Public ─────────────────────────────────────────────────────────────────── export function renderAssetCard(asset) { const container = document.getElementById('asset-card-container'); container.innerHTML = buildCardHTML(asset); } // ── Helpers ────────────────────────────────────────────────────────────────── function prop(asset, key) { return asset?.properties?.[key] ?? null; } function buildCardHTML(a) { const possessionStatus = prop(a, 'Possession Status'); const lifecycleStage = prop(a, 'Lifecycle Stage'); const lastScanDate = prop(a, 'Last Scan Date'); const lastAction = prop(a, 'Last Action'); const assetHistory = prop(a, 'Asset History'); const infraTag = prop(a, 'Tag'); const infraLocation = prop(a, 'Location'); const isInfra = prop(a, 'Infrastructure') === 'Yes'; const contactName = a.contact_fullname ?? a.contact?.name ?? null; const rawLastUser = a.properties?.kabuto_information?.last_user ?? ''; const lastUser = normalizeUsername(rawLastUser); const allContactNames = getCustomerContactNames(a.customer_id); const sameUser = !!(lastUser && contactName && ( usernameFuzzyMatch(rawLastUser, contactName) || usernameFirstNameMatch(rawLastUser, contactName, allContactNames) || usernameNameInitialMatch(rawLastUser, contactName, allContactNames) )); const contactEmail = a.contact?.email ?? null; const customerName = a.customer?.business_name ?? a.customer?.business_then_name ?? a.customer?.name ?? '—'; const serialNumber = a.asset_serial ?? a.serial ?? a.serial_number ?? '—'; const assetType = a.properties?.form_factor ?? a.properties?.kabuto_information?.general?.form_factor ?? a.asset_type ?? 'Device'; const syncroUrl = `${baseUrl}/customer_assets/${a.id}`; return `
${esc(a.name)}
${esc(assetType)} ${esc(customerName)}
Possession
${possessionBadge(possessionStatus)}
Lifecycle
${lifecycleBadge(lifecycleStage)}
${(!isInfra && lastUser) ? `
Last Login
${esc(sameUser ? contactName : lastUser)}
` : ''}
Last Sync
${a.properties?.kabuto_information?.last_synced_at ? formatDate(a.properties.kabuto_information.last_synced_at) : 'Never'}
Serial Number
${esc(serialNumber)}
${infraLocation ? `
Location
${esc(infraLocation)}
` : ''} ${!isInfra ? `
Assigned User
${ contactName ? `${esc(contactName)}${contactEmail ? `
${esc(contactEmail)}` : ''}` : 'Unassigned' }
` : ''}
Asset Type
${esc(assetType)}
Customer
${esc(customerName)}
${infraTag ? `
Tags
${infraTag.split(',').map(t => t.trim()).filter(Boolean).map(t => `${esc(t)}`).join('')}
` : ''}
Last Scan
${lastScanDate ? formatDate(lastScanDate) : 'Not set'}
Last Action
${lastAction ? esc(lastAction) : 'None recorded'}
${a.warranty_expires_at ? `
Warranty Expires
${formatDate(a.warranty_expires_at)}
` : ''}
Actions
${['Pre-Deployment','Inventory','Active','For Repair','For Upgrade','For Parts','Decommissioned','Disposed of'].map(stage => { const dotCls = 'lc-dot-' + stage.toLowerCase().replace(/\s+/g,'-').replace(/[^a-z0-9-]/g,''); const isCurrent = lifecycleStage === stage; return ``; }).join('')}
${contactName ? `` : ''}
Select Contact
${iconSpinner()} Loading contacts…
${isInfra ? 'Manage Infrastructure' : 'Set Infrastructure'}
${iconSpinner()} Loading tickets…
${buildHistoryHTML(assetHistory)}
`; } // ── Badge builders ──────────────────────────────────────────────────────────── function possessionBadge(status) { const cls = !status ? 'badge-unknown' : status === 'In IT Possession' ? 'badge-it-possession' : (status === 'Deployed' || status === 'In User Possession') ? 'badge-user-possession' : 'badge-unknown'; const labelInner = !status ? 'Unknown' : status === 'In IT Possession' ? `${iconCheck()} In IT Possession` : (status === 'Deployed' || status === 'In User Possession') ? `${iconUser()} Deployed` : esc(status); const chevron = ``; const options = ['In IT Possession', 'Deployed'].map(opt => { const isCurrent = status === opt || (opt === 'Deployed' && status === 'In User Possession'); return ``; }).join(''); return `
${options}
`; } function lifecycleBadge(stage) { const map = { 'Pre-Deployment':'badge-pre-deployment', 'Inventory': 'badge-inventory', 'Active': 'badge-active', 'For Repair': 'badge-for-repair', 'For Upgrade': 'badge-for-upgrade', 'Decommissioned':'badge-decommissioned', 'For Parts': 'badge-for-parts', 'Disposed of': 'badge-disposed-of', }; const cls = !stage ? 'badge-unknown' : (map[stage] ?? 'badge-unknown'); const chevron = ``; const stages = ['Pre-Deployment','Inventory','Active','For Repair','For Upgrade','For Parts','Decommissioned','Disposed of']; const options = stages.map(s => { const dotCls = 'lc-dot-' + s.toLowerCase().replace(/\s+/g,'-').replace(/[^a-z0-9-]/g,''); const isCurrent = stage === s; return ``; }).join(''); return `
${options}
`; } function buildHistoryHTML(rawValue) { if (!rawValue || !rawValue.trim()) { return `
No history recorded yet.
`; } const entries = rawValue.trim().split('\n').filter(Boolean); return entries.map(line => { // Expected format: [YYYY-MM-DD HH:MM] — description const match = line.match(/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2})\] — (.+)$/); if (match) { return `
${esc(match[2])}
${esc(match[1])}
`; } return `
${esc(line)}
`; }).join(''); } // ── Tiny inline icons (SVG) ─────────────────────────────────────────────────── function iconExternal() { return ``; } function iconSwap() { return ``; } function iconStages() { return ``; } function iconPerson() { return ``; } function iconSignOut() { return ``; } function iconPrint() { return ``; } function iconQueuePlus() { return ``; } function iconCheck() { return ``; } function iconUser() { return ``; } function iconUserRemove() { return ``; } function iconServer() { return ``; } function iconSpinner() { return ``; } // ── Utils ───────────────────────────────────────────────────────────────────── function esc(str) { if (!str) return ''; return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } function formatDate(dateStr) { if (!dateStr) return '—'; try { return new Date(dateStr).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); } catch { return dateStr; } }