data&device ready

This commit is contained in:
“yuanmengy”
2026-06-05 17:11:56 +08:00
commit a225272d1e
29 changed files with 49446 additions and 0 deletions

578
scripts/session.js Normal file
View File

@@ -0,0 +1,578 @@
/**
* TalkingQ Platform - Session Detail Page
*/
const SESSION_TOPIC_DRILLDOWN_KEY = 'talkingq_session_topic_drilldown';
const SESSION_VIEW_STATE_KEY = 'talkingq_session_view_state';
const SESSION_DEFAULT_DATE = '2026-05-25';
const SESSION_TOPIC_OPTIONS = ['讲故事', '唱儿歌', '问答学习', '天气查询', '闹钟设置', '游戏互动', '英语学习', '百科问答', '情感陪伴', '其他'];
function getMockSessionData() {
const rows = [
['sess-a251', 'company_a', '智慧星科技', 'DEV-A001', '小Q故事机器人', '2026-05-25 14:22', '讲故事', 12, '8m 32s', 'good'],
['sess-a252', 'company_a', '智慧星科技', 'DEV-A001', '小Q故事机器人', '2026-05-25 10:12', '百科问答', 8, '5m 28s', 'good'],
['sess-a253', 'company_a', '智慧星科技', 'DEV-A005', '英语学习助手', '2026-05-25 13:48', '英语学习', 7, '5m 10s', 'good'],
['sess-a254', 'company_a', '智慧星科技', 'DEV-A008', '小Q音乐伙伴', '2026-05-25 13:08', '唱儿歌', 10, '6m 48s', 'good'],
['sess-a255', 'company_a', '智慧星科技', 'DEV-A011', '生活助手', '2026-05-25 12:36', '天气查询', 6, '4m 12s', 'neutral'],
['sess-a256', 'company_a', '智慧星科技', 'DEV-A018', '设备小助手', '2026-05-25 11:52', '闹钟设置', 4, '2m 20s', 'good'],
['sess-a257', 'company_a', '智慧星科技', 'DEV-A021', '小Q游戏伙伴', '2026-05-25 11:18', '游戏互动', 15, '11m 05s', 'good'],
['sess-a258', 'company_a', '智慧星科技', 'DEV-A026', '陪伴机器人', '2026-05-25 10:40', '情感陪伴', 8, '5m 44s', 'good'],
['sess-a241', 'company_a', '智慧星科技', 'DEV-A001', '小Q故事机器人', '2026-05-24 16:06', '讲故事', 9, '6m 16s', 'good'],
['sess-a242', 'company_a', '智慧星科技', 'DEV-A005', '英语学习助手', '2026-05-24 14:20', '问答学习', 6, '4m 38s', 'neutral'],
['sess-a231', 'company_a', '智慧星科技', 'DEV-A021', '小Q游戏伙伴', '2026-05-23 19:42', '游戏互动', 13, '9m 52s', 'good'],
['sess-b251', 'company_b', '未来玩具厂', 'DEV-B002', '学习机助手', '2026-05-25 14:05', '问答学习', 3, '1m 45s', 'bad'],
['sess-b252', 'company_b', '未来玩具厂', 'DEV-B004', '科学百科探索者', '2026-05-25 13:42', '百科问答', 9, '7m 03s', 'neutral'],
['sess-b253', 'company_b', '未来玩具厂', 'DEV-B008', '小Q故事机器人', '2026-05-25 13:18', '讲故事', 11, '8m 15s', 'good'],
['sess-b254', 'company_b', '未来玩具厂', 'DEV-B015', '小Q音乐伙伴', '2026-05-25 12:46', '唱儿歌', 8, '4m 50s', 'good'],
['sess-b255', 'company_b', '未来玩具厂', 'DEV-B019', '小Q游戏伙伴', '2026-05-25 11:56', '游戏互动', 14, '10m 22s', 'good'],
['sess-b256', 'company_b', '未来玩具厂', 'DEV-B024', '生活助手', '2026-05-25 11:20', '天气查询', 5, '3m 06s', 'neutral'],
['sess-b241', 'company_b', '未来玩具厂', 'DEV-B008', '小Q故事机器人', '2026-05-24 15:32', '讲故事', 10, '7m 30s', 'good'],
['sess-b242', 'company_b', '未来玩具厂', 'DEV-B004', '科学百科探索者', '2026-05-24 12:14', '百科问答', 7, '5m 48s', 'good'],
['sess-c251', 'company_c', '童趣电子', 'DEV-C001', '小Q故事机器人', '2026-05-25 13:55', '讲故事', 18, '15m 20s', 'good'],
['sess-c252', 'company_c', '童趣电子', 'DEV-C006', '小Q音乐伙伴', '2026-05-25 13:08', '唱儿歌', 9, '5m 35s', 'good'],
['sess-c253', 'company_c', '童趣电子', 'DEV-C012', '学习机助手', '2026-05-25 12:32', '问答学习', 10, '6m 18s', 'good'],
['sess-c254', 'company_c', '童趣电子', 'DEV-C018', '英语学习助手', '2026-05-25 11:54', '英语学习', 6, '4m 27s', 'neutral'],
['sess-c255', 'company_c', '童趣电子', 'DEV-C022', '科学百科探索者', '2026-05-25 11:16', '百科问答', 12, '9m 11s', 'good'],
['sess-c256', 'company_c', '童趣电子', 'DEV-C027', '陪伴机器人', '2026-05-25 10:45', '情感陪伴', 7, '4m 58s', 'good'],
['sess-c257', 'company_c', '童趣电子', 'DEV-C031', '小Q助手', '2026-05-25 10:20', '其他', 2, '1m 02s', 'neutral'],
['sess-c241', 'company_c', '童趣电子', 'DEV-C001', '小Q故事机器人', '2026-05-24 18:18', '讲故事', 16, '13m 44s', 'good'],
['sess-c242', 'company_c', '童趣电子', 'DEV-C012', '学习机助手', '2026-05-24 09:48', '问答学习', 9, '6m 01s', 'good'],
];
return rows.map(([id, companyId, companyName, device, agent, start, intent, turns, duration, quality]) => ({
id,
companyId,
companyName,
device,
agent,
start,
date: start.slice(0, 10),
intent,
turns,
duration,
quality,
}));
}
function readSessionTopicDrilldown() {
try {
const raw = sessionStorage.getItem(SESSION_TOPIC_DRILLDOWN_KEY);
if (!raw) return null;
const parsed = JSON.parse(raw);
if (!parsed?.topic) return null;
if (parsed.createdAt && Date.now() - parsed.createdAt > 30 * 60 * 1000) {
sessionStorage.removeItem(SESSION_TOPIC_DRILLDOWN_KEY);
return null;
}
return parsed;
} catch (error) {
sessionStorage.removeItem(SESSION_TOPIC_DRILLDOWN_KEY);
return null;
}
}
function readSessionViewState() {
try {
return JSON.parse(sessionStorage.getItem(SESSION_VIEW_STATE_KEY) || '{}') || {};
} catch (error) {
return {};
}
}
function updateSessionViewState(patch) {
const next = { ...readSessionViewState(), ...patch };
sessionStorage.setItem(SESSION_VIEW_STATE_KEY, JSON.stringify(next));
}
function setSessionTopicDrilldown(topic, context = {}) {
if (!topic) {
sessionStorage.removeItem(SESSION_TOPIC_DRILLDOWN_KEY);
return;
}
sessionStorage.setItem(SESSION_TOPIC_DRILLDOWN_KEY, JSON.stringify({
topic,
companyId: context.companyId || 'all',
companyName: context.companyName || '全部公司',
source: context.source || 'session-filter',
createdAt: Date.now(),
}));
}
function getSessionCurrentCompanyId() {
const currentRole = window.__getUserRole ? window.__getUserRole() : 'admin';
const currentCompanyId = window.__getCurrentCompanyId ? window.__getCurrentCompanyId() : 'all';
if (currentRole !== 'client') return currentCompanyId;
if (currentCompanyId !== 'all') return currentCompanyId;
try {
const user = JSON.parse(sessionStorage.getItem('talkingq_user') || 'null');
return user?.companyId || user?.allowedCompanies?.[0] || currentCompanyId;
} catch (error) {
return currentCompanyId;
}
}
function getSessionCompanyName(companyId, fallback = '当前公司') {
if (companyId === 'all') return '全部公司';
return window.__getCompanyNameById ? window.__getCompanyNameById(companyId) : fallback;
}
function sessionMatchesTopic(session, topic) {
if (!topic) return true;
const intent = String(session.intent || '');
if (intent === topic) return true;
if (intent.includes(topic) || topic.includes(intent)) return true;
const aliases = {
'讲故事': ['故事', '睡前故事', '绘本'],
'唱儿歌': ['儿歌', '音乐', '唱歌'],
'问答学习': ['问答', '学习', '作业'],
'天气查询': ['天气'],
'闹钟设置': ['闹钟', '提醒'],
'游戏互动': ['游戏', '互动'],
'英语学习': ['英语', '英文'],
'百科问答': ['百科', '科普'],
'情感陪伴': ['陪伴', '情感', '聊天'],
'其他': ['其他'],
};
return (aliases[topic] || []).some(keyword => intent.includes(keyword));
}
function sessionEscapeHtml(value) {
return String(value ?? '').replace(/[&<>"']/g, char => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
}[char]));
}
function sessionEscapeAttr(value) {
return sessionEscapeHtml(value).replace(/"/g, '&quot;');
}
function formatSessionDateLabel(date) {
const [year, month, day] = String(date || '').split('-');
return year && month && day ? `${year}.${month}.${day}` : date;
}
function getSessionQualityText(quality) {
if (quality === 'good') return '优质';
if (quality === 'bad') return '待优化';
return '正常';
}
function getSessionQualityBadge(quality) {
if (quality === 'good') return 'badge-success';
if (quality === 'bad') return 'badge-danger';
return 'badge-default';
}
function getTopicOptionsForCompany(sessions) {
const seen = new Set(sessions.map(item => item.intent));
return SESSION_TOPIC_OPTIONS.filter(topic => seen.has(topic));
}
function getCompanyDevices(companySessions, daySessions) {
const byDevice = new Map();
companySessions.forEach(session => {
if (!byDevice.has(session.device)) {
byDevice.set(session.device, {
device: session.device,
companyId: session.companyId,
companyName: session.companyName,
agent: session.agent,
sessions: [],
});
}
});
daySessions.forEach(session => {
if (!byDevice.has(session.device)) {
byDevice.set(session.device, {
device: session.device,
companyId: session.companyId,
companyName: session.companyName,
agent: session.agent,
sessions: [],
});
}
byDevice.get(session.device).sessions.push(session);
});
return Array.from(byDevice.values()).map(item => {
const topics = Array.from(new Set(item.sessions.map(session => session.intent)));
const turns = item.sessions.reduce((sum, session) => sum + session.turns, 0);
const lastSession = item.sessions.slice().sort((a, b) => b.start.localeCompare(a.start))[0];
return {
...item,
topics,
turns,
count: item.sessions.length,
lastSession,
};
}).sort((a, b) => b.count - a.count || a.device.localeCompare(b.device));
}
function getConversationLines(topic) {
const samples = {
'讲故事': ['小Q给我讲一个关于恐龙的故事吧', '好的!从前,在一片茂密的丛林里,住着一只叫做小雷的霸王龙宝宝……', '霸王龙是最厉害的恐龙吗?', '霸王龙很厉害,但恐龙世界里还有很多不同种类的大家伙。我们可以一起认识它们。'],
'唱儿歌': ['小Q唱一首上学路上的儿歌。', '当然可以,我会用轻快一点的节奏来唱。', '再唱一首动物相关的。', '好的,这次我们唱一首关于小兔子的儿歌。'],
'问答学习': ['小Q为什么月亮会变圆又变弯', '这是因为月亮围着地球转,我们看到被太阳照亮的部分会变化。', '那满月是什么时候?', '当我们看到月亮被照亮的一面几乎完整朝向地球时,就是满月。'],
'天气查询': ['小Q今天要带雨伞吗', '今天下午可能有小雨,建议带一把轻便雨伞。', '那适合出去玩吗?', '可以短时间外出,尽量避开下午降雨时段。'],
'闹钟设置': ['小Q明天早上七点叫我起床。', '好的,已为你设置明天早上七点的起床提醒。', '再加一个七点十分。', '已添加七点十分的第二个提醒。'],
'游戏互动': ['小Q我们玩猜动物吧。', '好,我来描述一个动物,你来猜。它有长长的鼻子和大大的耳朵。', '是大象!', '答对了!下一题要不要难一点?'],
'英语学习': ['How do I say 小熊 in English?', '小熊 can be said as little bear or teddy bear, depending on context.', 'Can you make a sentence?', 'Sure. The little bear is sleeping under the tree.'],
'百科问答': ['小Q火山为什么会喷发', '地下的岩浆和气体压力变大时,会沿着裂缝冲出来,就形成了火山喷发。', '岩浆冷了会变成什么?', '岩浆冷却后会变成岩石,有些还会形成新的地貌。'],
'情感陪伴': ['小Q我今天有点不开心。', '我在呢。你愿意告诉我发生了什么吗?', '我和朋友吵架了。', '听起来你很难过。我们可以先深呼吸,再想想怎么和朋友好好说。'],
'其他': ['小Q你现在能做什么', '我可以聊天、讲故事、唱儿歌、回答问题,也可以帮你做一些简单提醒。', '那我们随便聊聊吧。', '好呀,你今天最开心的一件事是什么?'],
};
return samples[topic] || samples['其他'];
}
function renderSessionConversation(session, options = {}) {
if (!session) {
return '<div class="empty-state" style="padding:40px 12px"><p>暂无可查看的对话内容</p></div>';
}
const lines = getConversationLines(session.intent);
const hiddenTurns = Math.max(session.turns - 4, 0);
return `
<div class="session-message user">
<div class="session-message-avatar">用户</div>
<div class="session-message-text">${sessionEscapeHtml(lines[0])}</div>
</div>
<div class="session-message bot">
<div class="session-message-avatar">Q</div>
<div class="session-message-text">${sessionEscapeHtml(lines[1])}</div>
</div>
<div class="session-message user">
<div class="session-message-avatar">用户</div>
<div class="session-message-text">${sessionEscapeHtml(lines[2])}</div>
</div>
<div class="session-message bot">
<div class="session-message-avatar">Q</div>
<div class="session-message-text">${sessionEscapeHtml(lines[3])}</div>
</div>
${options.showMore !== false ? `<div class="session-more">还有 ${hiddenTurns} 轮对话</div>` : ''}
`;
}
function renderDeviceRows(devices, selectedDeviceId, showCompanyColumn) {
if (!devices.length) {
return `<tr><td colspan="${showCompanyColumn ? 7 : 6}" style="text-align:center;padding:40px;color:var(--text-muted)">暂无设备数据</td></tr>`;
}
return devices.map(device => {
const active = device.device === selectedDeviceId;
return `
<tr class="${active ? 'session-device-row active' : 'session-device-row'}" data-device-id="${sessionEscapeAttr(device.device)}">
<td>
<div style="font-weight:600">${sessionEscapeHtml(device.device)}</div>
<div style="font-size:12px;color:var(--text-muted)">${sessionEscapeHtml(device.agent)}</div>
</td>
${showCompanyColumn ? `<td><span class="badge badge-default">${sessionEscapeHtml(getSessionCompanyName(device.companyId, device.companyName))}</span></td>` : ''}
<td><span class="badge badge-info">${device.count} 条</span></td>
<td style="font-weight:600">${device.turns} 轮</td>
<td>${device.topics.length ? device.topics.slice(0, 3).map(topic => `<span class="badge badge-default" style="margin-right:4px">${sessionEscapeHtml(topic)}</span>`).join('') : '<span style="color:var(--text-muted)">当天无对话</span>'}</td>
<td style="font-size:12px;color:var(--text-muted)">${device.lastSession ? sessionEscapeHtml(device.lastSession.start.slice(11)) : '-'}</td>
<td><button class="btn btn-outline btn-sm view-device-btn" data-device-id="${sessionEscapeAttr(device.device)}">查看</button></td>
</tr>
`;
}).join('');
}
function renderDeviceSessionList(device, selectedDate) {
if (!device) {
return `
<div class="session-device-panel">
<div class="session-panel-header">
<div>
<h3>设备对话历史</h3>
<p>${formatSessionDateLabel(selectedDate)} · 从左侧选择设备查看对话内容</p>
</div>
</div>
<div class="empty-state" style="padding:56px 16px"><p>请选择设备查看当天对话历史</p></div>
</div>
`;
}
if (!device.sessions.length) {
return `
<div class="session-device-panel">
<div class="session-panel-header">
<div>
<h3>${sessionEscapeHtml(device.device)}</h3>
<p>${formatSessionDateLabel(selectedDate)} 当天暂无对话</p>
</div>
<button class="btn btn-outline btn-sm" data-session-back-device>
<i data-lucide="arrow-left"></i> 返回设备列表
</button>
</div>
<div class="empty-state" style="padding:42px 16px"><p>该设备在所选日期没有产生对话</p></div>
</div>
`;
}
return `
<div class="session-device-panel">
<div class="session-panel-header">
<div>
<h3>${sessionEscapeHtml(device.device)}</h3>
<p>${formatSessionDateLabel(selectedDate)} · ${device.sessions.length} 条对话 · ${device.turns} 轮互动</p>
</div>
<div class="session-panel-actions">
<span class="badge badge-success">${sessionEscapeHtml(device.agent)}</span>
<button class="btn btn-outline btn-sm" data-session-back-device>
<i data-lucide="arrow-left"></i> 返回设备列表
</button>
</div>
</div>
<div class="session-history-list">
${device.sessions.map(session => `
<div class="session-history-card">
<div class="session-history-head">
<div>
<strong>${sessionEscapeHtml(session.intent)}</strong>
<span>${sessionEscapeHtml(session.start.slice(11))} · ${session.turns} 轮 · ${sessionEscapeHtml(session.duration)}</span>
</div>
<span class="badge ${getSessionQualityBadge(session.quality)}">${getSessionQualityText(session.quality)}</span>
</div>
<div class="session-transcript">
${renderSessionConversation(session)}
</div>
</div>
`).join('')}
</div>
</div>
`;
}
function renderTopicAggregateView(sessions, selectedTopic, selectedDate, companyName) {
const totalTurns = sessions.reduce((sum, session) => sum + session.turns, 0);
const agents = new Set(sessions.map(session => session.agent));
return `
<div class="session-topic-layout">
<div class="session-topic-summary">
<div>
<span>话题</span>
<strong>${sessionEscapeHtml(selectedTopic)}</strong>
</div>
<div>
<span>日期</span>
<strong>${formatSessionDateLabel(selectedDate)}</strong>
</div>
<div>
<span>会话数</span>
<strong>${sessions.length}</strong>
</div>
<div>
<span>总轮次</span>
<strong>${totalTurns}</strong>
</div>
</div>
<div class="chart-card full">
<div class="card-header">
<div>
<div class="card-title">「${sessionEscapeHtml(selectedTopic)}」对话内容整合</div>
<div class="card-subtitle">${sessionEscapeHtml(companyName)} 全部设备聚合 · ${agents.size} 个智能体参与 · 不按设备区分</div>
</div>
</div>
${sessions.length ? `
<div class="session-aggregate-list">
${sessions.map(session => `
<div class="session-aggregate-card">
<div class="session-history-head">
<div>
<strong>${sessionEscapeHtml(session.agent)}</strong>
<span>${sessionEscapeHtml(session.start.slice(11))} · ${session.turns} 轮 · ${sessionEscapeHtml(session.duration)}</span>
</div>
<span class="badge ${getSessionQualityBadge(session.quality)}">${getSessionQualityText(session.quality)}</span>
</div>
<div class="session-transcript">
${renderSessionConversation(session)}
</div>
</div>
`).join('')}
</div>
` : `<div class="empty-state" style="padding:56px 16px"><p>所选日期暂无「${sessionEscapeHtml(selectedTopic)}」相关对话</p></div>`}
</div>
</div>
`;
}
registerPage('session', function(container) {
const currentRole = window.__getUserRole ? window.__getUserRole() : 'admin';
const isClient = currentRole === 'client';
const currentCompanyId = getSessionCurrentCompanyId();
const currentCompanyName = getSessionCompanyName(currentCompanyId, '当前公司');
const viewState = readSessionViewState();
const topicDrilldown = readSessionTopicDrilldown();
const selectedTopic = topicDrilldown?.topic || '';
const fromDashboardDrilldown = topicDrilldown?.source === 'dashboard-topic-ranking';
const selectedDate = viewState.date || topicDrilldown?.date || SESSION_DEFAULT_DATE;
const allSessions = getMockSessionData();
const companySessions = currentCompanyId === 'all'
? allSessions
: allSessions.filter(session => session.companyId === currentCompanyId);
const daySessions = companySessions.filter(session => session.date === selectedDate);
const topicSessions = selectedTopic
? daySessions.filter(session => sessionMatchesTopic(session, selectedTopic))
: [];
const devices = getCompanyDevices(companySessions, daySessions);
const selectedDeviceId = selectedTopic
? ''
: (viewState.deviceId && devices.some(device => device.device === viewState.deviceId) ? viewState.deviceId : '');
const selectedDevice = devices.find(device => device.device === selectedDeviceId);
const showCompanyColumn = !isClient && currentCompanyId === 'all';
const topicOptions = getTopicOptionsForCompany(companySessions);
const deviceCount = devices.length;
const dayTurns = daySessions.reduce((sum, session) => sum + session.turns, 0);
const pageDesc = selectedTopic
? `${currentCompanyName} · 按热门话题聚合查看对话内容`
: `${currentCompanyName} · 按设备查看所选日期的对话历史`;
container.innerHTML = `
<div class="page-header">
<div>
<div class="page-title">会话详情</div>
<div class="page-desc">${sessionEscapeHtml(pageDesc)}</div>
</div>
</div>
${selectedTopic ? `
<div class="session-view-backbar">
<button class="btn btn-outline btn-sm" id="sessionStepBackBtn">
<i data-lucide="arrow-left"></i> ${fromDashboardDrilldown ? '返回数据仪表盘' : '返回全部话题'}
</button>
</div>
<div class="session-drilldown-banner">
<div class="session-drilldown-icon"><i data-lucide="bar-chart-3"></i></div>
<div class="session-drilldown-copy">
<strong>热门话题聚合:${sessionEscapeHtml(selectedTopic)}</strong>
<span>${sessionEscapeHtml(currentCompanyName)} · ${formatSessionDateLabel(selectedDate)} · 当前结果 ${topicSessions.length} 条 · 不区分设备</span>
</div>
</div>
` : ''}
<div class="session-filter-panel">
<div class="session-filter-main">
<div class="form-group">
<label class="form-label">日期</label>
<input type="date" class="form-control" id="sessionDateFilter" value="${sessionEscapeAttr(selectedDate)}">
</div>
<div class="form-group">
<label class="form-label">热门话题</label>
<select class="form-control" id="sessionTopicFilter">
<option value="">全部话题</option>
${topicOptions.map(topic => `<option value="${sessionEscapeAttr(topic)}" ${topic === selectedTopic ? 'selected' : ''}>${sessionEscapeHtml(topic)}</option>`).join('')}
</select>
</div>
</div>
<div class="session-filter-stats">
<div>
<span>设备</span>
<strong>${deviceCount}</strong>
</div>
<div>
<span>当日会话</span>
<strong>${selectedTopic ? topicSessions.length : daySessions.length}</strong>
</div>
<div>
<span>互动轮次</span>
<strong>${selectedTopic ? topicSessions.reduce((sum, session) => sum + session.turns, 0) : dayTurns}</strong>
</div>
</div>
</div>
${selectedTopic ? renderTopicAggregateView(topicSessions, selectedTopic, selectedDate, currentCompanyName) : `
<div class="session-device-layout">
<div class="chart-card">
<div class="card-header">
<div>
<div class="card-title">全部设备</div>
<div class="card-subtitle">${formatSessionDateLabel(selectedDate)} · 点击设备查看当天对话历史</div>
</div>
</div>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>设备</th>
${showCompanyColumn ? '<th>所属公司</th>' : ''}
<th>当日会话</th>
<th>轮次</th>
<th>涉及话题</th>
<th>最后对话</th>
<th>操作</th>
</tr>
</thead>
<tbody>${renderDeviceRows(devices, selectedDeviceId, showCompanyColumn)}</tbody>
</table>
</div>
</div>
<div class="chart-card">
${renderDeviceSessionList(selectedDevice, selectedDate)}
</div>
</div>
`}
`;
lucide.createIcons({ elements: [container] });
const backToAllTopics = () => {
sessionStorage.removeItem(SESSION_TOPIC_DRILLDOWN_KEY);
navigateTo('session');
};
container.querySelector('#sessionStepBackBtn')?.addEventListener('click', () => {
sessionStorage.removeItem(SESSION_TOPIC_DRILLDOWN_KEY);
if (fromDashboardDrilldown) {
navigateTo('dashboard');
return;
}
navigateTo('session');
});
container.querySelector('#clearSessionTopicFilter')?.addEventListener('click', backToAllTopics);
container.querySelectorAll('[data-session-back-topic]').forEach(button => {
button.addEventListener('click', backToAllTopics);
});
container.querySelectorAll('[data-session-back-device]').forEach(button => {
button.addEventListener('click', () => {
updateSessionViewState({ deviceId: '' });
navigateTo('session');
});
});
container.querySelector('#sessionDateFilter')?.addEventListener('change', event => {
updateSessionViewState({ date: event.target.value || SESSION_DEFAULT_DATE, deviceId: '' });
navigateTo('session');
});
container.querySelector('#sessionTopicFilter')?.addEventListener('change', event => {
const topic = event.target.value;
setSessionTopicDrilldown(topic, {
companyId: currentCompanyId,
companyName: currentCompanyName,
source: 'session-filter',
date: selectedDate,
});
navigateTo('session');
});
const selectDevice = deviceId => {
if (!deviceId) return;
updateSessionViewState({ deviceId });
navigateTo('session');
};
container.querySelectorAll('.session-device-row').forEach(row => {
row.addEventListener('click', () => {
selectDevice(row.dataset.deviceId);
});
});
container.querySelectorAll('.view-device-btn').forEach(button => {
button.addEventListener('click', event => {
event.stopPropagation();
selectDevice(button.dataset.deviceId);
});
});
});