95 lines
3.6 KiB
JavaScript
95 lines
3.6 KiB
JavaScript
const state = { data: null };
|
|
|
|
const statusLabels = {
|
|
normal: "正常",
|
|
risk: "有风险",
|
|
need_help: "需要支持"
|
|
};
|
|
|
|
function text(value) {
|
|
return String(value || "");
|
|
}
|
|
|
|
function escapeHtml(value) {
|
|
return text(value).replace(/[&<>"']/g, (char) => ({
|
|
"&": "&",
|
|
"<": "<",
|
|
">": ">",
|
|
'"': """,
|
|
"'": "'"
|
|
}[char]));
|
|
}
|
|
|
|
function render() {
|
|
const employeeQuery = document.querySelector("#employee-filter").value.trim().toLowerCase();
|
|
const keywordQuery = document.querySelector("#keyword-filter").value.trim().toLowerCase();
|
|
const onlyBlockers = document.querySelector("#blocker-filter").checked;
|
|
const data = state.data;
|
|
|
|
document.querySelector("#stats").innerHTML = `
|
|
<strong>应提交:${data.expectedCount}</strong>
|
|
<strong>已提交:${data.submittedCount}</strong>
|
|
<strong>未提交:${data.missingCount}</strong>
|
|
`;
|
|
|
|
const reports = data.reports.filter((report) => {
|
|
const employeeMatch = report.employee_name.toLowerCase().includes(employeeQuery);
|
|
const searchable = [
|
|
report.today_done,
|
|
report.tomorrow_plan,
|
|
report.blockers,
|
|
report.help_needed,
|
|
statusLabels[report.report_status]
|
|
].join(" ").toLowerCase();
|
|
const keywordMatch = searchable.includes(keywordQuery);
|
|
const blockerMatch = !onlyBlockers || report.blockers || report.help_needed || report.report_status === "need_help";
|
|
return employeeMatch && keywordMatch && blockerMatch;
|
|
});
|
|
|
|
document.querySelector("#reports").innerHTML = reports.length ? reports.map((report) => `
|
|
<article class="report-card">
|
|
<div class="report-head">
|
|
<h3>${escapeHtml(report.employee_name)}</h3>
|
|
<span class="status-badge status-${escapeHtml(report.report_status || "normal")}">
|
|
${statusLabels[report.report_status] || "正常"}
|
|
</span>
|
|
</div>
|
|
<div class="report-grid">
|
|
<div><div class="field-title">今日完成</div><pre>${escapeHtml(report.today_done)}</pre></div>
|
|
<div><div class="field-title">明日计划</div><pre>${escapeHtml(report.tomorrow_plan)}</pre></div>
|
|
<div><div class="field-title">遇到的问题</div><pre>${escapeHtml(report.blockers || "无")}</pre></div>
|
|
<div><div class="field-title">需要协助</div><pre>${escapeHtml(report.help_needed || "无")}</pre></div>
|
|
</div>
|
|
<p>提交时间:${escapeHtml(report.updated_at)}</p>
|
|
</article>
|
|
`).join("") : "<p>当前筛选下没有日报。</p>";
|
|
|
|
document.querySelector("#missing").textContent = data.missing.length
|
|
? data.missing.map((employee) => employee.name).join("、")
|
|
: "无";
|
|
}
|
|
|
|
async function loadReports() {
|
|
const date = document.querySelector("#date-filter").value;
|
|
document.querySelector("#export-link").href = `/api/reports/export?date=${date}`;
|
|
const response = await fetch(`/api/reports?date=${date}`);
|
|
state.data = await response.json();
|
|
render();
|
|
}
|
|
|
|
document.querySelector("#date-filter").addEventListener("change", loadReports);
|
|
document.querySelector("#employee-filter").addEventListener("input", render);
|
|
document.querySelector("#keyword-filter").addEventListener("input", render);
|
|
document.querySelector("#blocker-filter").addEventListener("change", render);
|
|
document.querySelector("#copy-summary").addEventListener("click", async () => {
|
|
const data = state.data;
|
|
const lines = [
|
|
`${data.date} 日报汇总`,
|
|
`已提交:${data.submittedCount}/${data.expectedCount}`,
|
|
`未提交:${data.missing.length ? data.missing.map((employee) => employee.name).join("、") : "无"}`
|
|
];
|
|
await navigator.clipboard.writeText(lines.join("\n"));
|
|
});
|
|
|
|
loadReports();
|