Update teacher profiles and record summaries

This commit is contained in:
Codex
2026-06-16 01:09:51 +08:00
parent a7fbe91266
commit 2785f3eb41
18 changed files with 1088 additions and 40 deletions
+84 -3
View File
@@ -4,7 +4,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>管理后台</title>
<link rel="stylesheet" href="/static/styles.css?v=20260615-summary-review-drawer" />
<link rel="stylesheet" href="/static/styles.css?v=20260615-summary-time-review" />
</head>
<body>
<header class="topbar">
@@ -22,6 +22,7 @@
<main class="layout account-layout admin-layout">
<nav class="admin-tabs" aria-label="管理后台功能">
<button class="admin-tab is-active" data-admin-tab="accounts" type="button">课时账户</button>
<button class="admin-tab" data-admin-tab="teachers" type="button">老师档案</button>
<button class="admin-tab" data-admin-tab="reviews" type="button">纠错审核</button>
<button class="admin-tab" data-admin-tab="summaries" type="button">课程小结审核</button>
<button class="admin-tab" data-admin-tab="summarySearch" type="button">课程小结查询</button>
@@ -110,6 +111,81 @@
</div>
</section>
<section id="teachersPanel" class="panel admin-panel" hidden>
<div class="section-head">
<h2>老师档案</h2>
<div class="quick-actions">
<select id="teacherStatus" aria-label="老师状态筛选">
<option value="">全部状态</option>
<option value="在岗">在岗</option>
<option value="离职">离职</option>
</select>
<button id="newTeacherBtn" class="chip" type="button">新增老师</button>
</div>
</div>
<form id="teacherForm" class="search-row account-search-row">
<input id="teacherQuery" autocomplete="off" placeholder="教师姓名、别名、教师ID或学科" />
<button type="submit">查询</button>
</form>
<div id="teacherMeta" class="summary-grid"></div>
<div id="teacherEditor" class="admin-editor" hidden>
<div class="section-head compact-head">
<h2 id="teacherEditorTitle">新增老师</h2>
<button id="cancelTeacherEditBtn" class="secondary-button" type="button">取消</button>
</div>
<form id="teacherEditForm" class="admin-form">
<div class="form-grid">
<label>
教师ID
<input id="editTeacherId" autocomplete="off" placeholder="新增时自动生成" />
</label>
<label>
教师姓名
<input id="editTeacherName" autocomplete="off" required />
</label>
<label>
别名
<input id="editTeacherAlias" autocomplete="off" placeholder="对外展示名称;空则显示教师姓名" />
</label>
<label>
状态
<select id="editTeacherStatus">
<option value="在岗">在岗</option>
<option value="离职">离职</option>
</select>
</label>
<label class="span-2">
任教学科
<input id="editTeacherSubjects" autocomplete="off" placeholder="数学、物理、生物" required />
</label>
<label class="span-2">
备注
<textarea id="editTeacherNote" rows="3"></textarea>
</label>
</div>
<p id="teacherEditError" class="correction-error" hidden></p>
<div class="modal-actions">
<button type="submit">保存老师档案</button>
</div>
</form>
</div>
<div class="table-wrap account-table-wrap">
<table>
<thead>
<tr>
<th>教师</th>
<th>别名</th>
<th>任教学科</th>
<th>状态</th>
<th>备注</th>
<th>操作</th>
</tr>
</thead>
<tbody id="teacherRows"></tbody>
</table>
</div>
</section>
<section id="reviewsPanel" class="panel admin-panel" hidden>
<div class="section-head">
<h2>纠错审核</h2>
@@ -223,6 +299,10 @@
<input id="summarySearchSubject" autocomplete="off" placeholder="科目" />
<input id="summarySearchDateFrom" type="date" aria-label="开始日期" />
<input id="summarySearchDateTo" type="date" aria-label="结束日期" />
<select id="summarySearchMissingTime" aria-label="时间状态筛选">
<option value="">全部时间</option>
<option value="1">缺少时间</option>
</select>
<button type="submit">查询</button>
</form>
<div id="summarySearchMeta" class="summary-grid"></div>
@@ -231,11 +311,12 @@
<thead>
<tr>
<th>日期</th>
<th>时间</th>
<th>学生</th>
<th>老师/科目</th>
<th>标题</th>
<th>小结正文</th>
<th>来源</th>
<th>操作</th>
</tr>
</thead>
<tbody id="summarySearchRows"></tbody>
@@ -319,6 +400,6 @@
</section>
</main>
<script src="/static/admin.js?v=20260615-summary-register-complete"></script>
<script src="/static/admin.js?v=20260615-summary-time-review"></script>
</body>
</html>
+215 -5
View File
@@ -2,6 +2,7 @@ const adminHealthText = document.querySelector("#adminHealthText");
const refreshBtn = document.querySelector("#refreshBtn");
const panels = {
accounts: document.querySelector("#accountsPanel"),
teachers: document.querySelector("#teachersPanel"),
reviews: document.querySelector("#reviewsPanel"),
summaries: document.querySelector("#summariesPanel"),
summarySearch: document.querySelector("#summarySearchPanel"),
@@ -19,6 +20,23 @@ const accountEditForm = document.querySelector("#accountEditForm");
const accountEditError = document.querySelector("#accountEditError");
const cancelAccountEditBtn = document.querySelector("#cancelAccountEditBtn");
const newAccountBtn = document.querySelector("#newAccountBtn");
const teacherForm = document.querySelector("#teacherForm");
const teacherQuery = document.querySelector("#teacherQuery");
const teacherStatus = document.querySelector("#teacherStatus");
const teacherMeta = document.querySelector("#teacherMeta");
const teacherRows = document.querySelector("#teacherRows");
const teacherEditor = document.querySelector("#teacherEditor");
const teacherEditorTitle = document.querySelector("#teacherEditorTitle");
const teacherEditForm = document.querySelector("#teacherEditForm");
const teacherEditError = document.querySelector("#teacherEditError");
const cancelTeacherEditBtn = document.querySelector("#cancelTeacherEditBtn");
const newTeacherBtn = document.querySelector("#newTeacherBtn");
const editTeacherId = document.querySelector("#editTeacherId");
const editTeacherName = document.querySelector("#editTeacherName");
const editTeacherAlias = document.querySelector("#editTeacherAlias");
const editTeacherStatus = document.querySelector("#editTeacherStatus");
const editTeacherSubjects = document.querySelector("#editTeacherSubjects");
const editTeacherNote = document.querySelector("#editTeacherNote");
const editStudentId = document.querySelector("#editStudentId");
const editStudent = document.querySelector("#editStudent");
const editRemaining = document.querySelector("#editRemaining");
@@ -50,6 +68,7 @@ const summarySearchTeacher = document.querySelector("#summarySearchTeacher");
const summarySearchSubject = document.querySelector("#summarySearchSubject");
const summarySearchDateFrom = document.querySelector("#summarySearchDateFrom");
const summarySearchDateTo = document.querySelector("#summarySearchDateTo");
const summarySearchMissingTime = document.querySelector("#summarySearchMissingTime");
const summarySearchMeta = document.querySelector("#summarySearchMeta");
const summarySearchRows = document.querySelector("#summarySearchRows");
const logOperation = document.querySelector("#logOperation");
@@ -71,6 +90,8 @@ const summaryRegisterStatus = document.querySelector("#summaryRegisterStatus");
let currentAccounts = [];
let editingAccountId = "";
let currentTeachers = [];
let editingTeacherId = "";
let currentSummaryReviews = [];
let activeSummaryReview = null;
let summaryRegisterItemSeq = 0;
@@ -147,6 +168,7 @@ function setActiveTab(tabName) {
panel.hidden = name !== tabName;
});
if (tabName === "accounts") loadAccounts();
if (tabName === "teachers") loadTeachers();
if (tabName === "reviews") loadReviews();
if (tabName === "summaries") loadSummaryReviews();
if (tabName === "summarySearch") loadSummarySearch();
@@ -156,7 +178,8 @@ function setActiveTab(tabName) {
async function loadAdminHealth() {
try {
const data = await fetchJson("/api/account-health");
adminHealthText.textContent = `账户 ${data.accounts_count} 人;数据更新时间 ${fmtTime(data.accounts.mtime)}`;
const teacherText = data.teachers_count === undefined ? "" : `;老师 ${data.teachers_count} `;
adminHealthText.textContent = `账户 ${data.accounts_count}${teacherText};数据更新时间 ${fmtTime(data.accounts.mtime)}`;
} catch (error) {
adminHealthText.textContent = `读取失败:${error.message}`;
}
@@ -291,6 +314,130 @@ async function saveAccount(event) {
}
}
function renderTeacherSummary(rows) {
const activeCount = rows.filter((item) => item.status === "在岗").length;
const aliasCount = rows.filter((item) => String(item.alias || "").trim()).length;
teacherMeta.innerHTML = [
metric("当前结果", `${rows.length}`),
metric("在岗", activeCount),
metric("已配置别名", aliasCount),
].join("");
}
function teacherMatches(row) {
const keyword = teacherQuery.value.trim();
if (teacherStatus.value && row.status !== teacherStatus.value) return false;
if (!keyword) return true;
const haystack = [
row.teacher_id,
row.name,
row.alias,
row.display_name,
...(row.subjects || []),
row.status,
row.note,
].join("\n");
return haystack.includes(keyword);
}
async function loadTeachers() {
teacherRows.innerHTML = `<tr><td colspan="6" class="empty">正在读取</td></tr>`;
try {
const data = await fetchJson("/api/admin/teachers");
currentTeachers = data.teachers || [];
const rows = currentTeachers.filter(teacherMatches);
renderTeacherSummary(rows);
teacherRows.innerHTML = rows
.map(
(row) => `<tr>
<td>${escapeHtml(row.name)}<br><small>${escapeHtml(row.teacher_id)}</small></td>
<td>${escapeHtml(row.alias || "")}</td>
<td>${escapeHtml((row.subjects || []).join("、"))}</td>
<td><span class="status ${row.status === "在岗" ? "normal" : "closed"}">${escapeHtml(row.status)}</span></td>
<td class="note-cell">${escapeHtml(row.note || "")}</td>
<td><button class="small-button teacher-edit" type="button" data-teacher-id="${escapeHtml(row.teacher_id)}">编辑</button></td>
</tr>`,
)
.join("");
if (!rows.length) {
teacherRows.innerHTML = `<tr><td colspan="6" class="empty">没有符合条件的老师档案</td></tr>`;
}
} catch (error) {
teacherRows.innerHTML = `<tr><td colspan="6" class="empty">读取失败:${escapeHtml(error.message)}</td></tr>`;
}
}
function openNewTeacherEditor() {
editingTeacherId = "";
teacherEditorTitle.textContent = "新增老师";
editTeacherId.value = "";
editTeacherId.placeholder = "保存时自动生成";
editTeacherName.value = "";
editTeacherAlias.value = "";
editTeacherSubjects.value = "";
editTeacherStatus.value = "在岗";
editTeacherNote.value = "";
teacherEditError.hidden = true;
teacherEditor.hidden = false;
editTeacherName.focus();
}
function openEditTeacherEditor(teacherId) {
const teacher = currentTeachers.find((item) => item.teacher_id === teacherId);
if (!teacher) return;
editingTeacherId = teacher.teacher_id;
teacherEditorTitle.textContent = `编辑老师:${teacher.name}`;
editTeacherId.value = teacher.teacher_id;
editTeacherId.placeholder = "";
editTeacherName.value = teacher.name;
editTeacherAlias.value = teacher.alias || "";
editTeacherSubjects.value = (teacher.subjects || []).join("、");
editTeacherStatus.value = teacher.status;
editTeacherNote.value = teacher.note || "";
teacherEditError.hidden = true;
teacherEditor.hidden = false;
editTeacherName.focus();
}
function parseTeacherSubjects(value) {
return value
.split(/[、,\s]+/)
.map((item) => item.trim())
.filter(Boolean);
}
function buildTeacherPayload() {
return {
teacher_id: editTeacherId.value.trim(),
name: editTeacherName.value.trim(),
alias: editTeacherAlias.value.trim(),
subjects: parseTeacherSubjects(editTeacherSubjects.value),
status: editTeacherStatus.value,
note: editTeacherNote.value.trim(),
};
}
async function saveTeacher(event) {
event.preventDefault();
try {
const payload = buildTeacherPayload();
const url = editingTeacherId
? `/api/admin/teachers/${encodeURIComponent(editingTeacherId)}`
: "/api/admin/teachers";
const method = editingTeacherId ? "PUT" : "POST";
await fetchJson(url, {
method,
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
teacherEditor.hidden = true;
await loadTeachers();
} catch (error) {
teacherEditError.textContent = error.message;
teacherEditError.hidden = false;
}
}
function renderReviewLine(line) {
return `<code class="line-code">${escapeHtml(line)}</code>`;
}
@@ -474,6 +621,7 @@ function summarySearchParams() {
if (summarySearchSubject.value.trim()) params.set("subject", summarySearchSubject.value.trim());
if (summarySearchDateFrom.value) params.set("date_from", summarySearchDateFrom.value);
if (summarySearchDateTo.value) params.set("date_to", summarySearchDateTo.value);
if (summarySearchMissingTime.value) params.set("missing_time", "true");
return params;
}
@@ -485,13 +633,25 @@ function renderSummarySearchBody(item) {
<button class="small-button summary-toggle" type="button" data-target="${escapeHtml(fullId)}" ${hasFull ? "" : "hidden"}>展开</button>`;
}
function renderSummaryActions(item) {
if (item.time_range) {
return `<span class="muted">已完整</span>`;
}
const inputId = `summary-time-${item.id}`;
return `<div class="summary-time-actions">
<input id="${escapeHtml(inputId)}" class="summary-time-input" data-summary-time-input="${escapeHtml(item.id)}" autocomplete="off" placeholder="08:00-10:00" />
<button class="small-button summary-time-save" type="button" data-summary-id="${escapeHtml(item.id)}">补齐时间</button>
<button class="small-button summary-delete" type="button" data-summary-id="${escapeHtml(item.id)}">删除</button>
</div>`;
}
function renderMatchedFields(item) {
const fields = Array.isArray(item.matched_fields) ? item.matched_fields : [];
return fields.length ? `<br><small>命中:${fields.map(escapeHtml).join("、")}</small>` : "";
}
async function loadSummarySearch() {
summarySearchRows.innerHTML = `<tr><td colspan="6" class="empty">正在读取</td></tr>`;
summarySearchRows.innerHTML = `<tr><td colspan="7" class="empty">正在读取</td></tr>`;
try {
const data = await fetchJson(`/api/admin/course-summaries?${summarySearchParams().toString()}`);
summarySearchMeta.innerHTML = [
@@ -501,21 +661,38 @@ async function loadSummarySearch() {
summarySearchRows.innerHTML = data.items
.map((item) => `<tr>
<td>${escapeHtml(item.date_iso || "未识别")}</td>
<td>${escapeHtml(item.time_range || "缺少时间")}</td>
<td>${escapeHtml(item.student || "")}<br><small>${escapeHtml(item.group || "")}</small></td>
<td>${escapeHtml(item.teacher || "待核对老师")}<br><small>${escapeHtml(item.subject || "待核对科目")}</small></td>
<td>${escapeHtml(item.title || "")}${renderMatchedFields(item)}</td>
<td>${renderSummarySearchBody(item)}</td>
<td><code class="line-code">${escapeHtml(item.relative_path || "")}</code></td>
<td>${renderSummaryActions(item)}</td>
</tr>`)
.join("");
if (!data.items.length) {
summarySearchRows.innerHTML = `<tr><td colspan="6" class="empty">没有符合条件的课程小结</td></tr>`;
summarySearchRows.innerHTML = `<tr><td colspan="7" class="empty">没有符合条件的课程小结</td></tr>`;
}
} catch (error) {
summarySearchRows.innerHTML = `<tr><td colspan="6" class="empty">读取失败:${escapeHtml(error.message)}</td></tr>`;
summarySearchRows.innerHTML = `<tr><td colspan="7" class="empty">读取失败:${escapeHtml(error.message)}</td></tr>`;
}
}
async function updateCourseSummaryTime(summaryId, timeRange) {
await fetchJson(`/api/admin/course-summaries/${encodeURIComponent(summaryId)}/time`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ time_range: timeRange }),
});
await loadSummarySearch();
}
async function deleteCourseSummary(summaryId) {
await fetchJson(`/api/admin/course-summaries/${encodeURIComponent(summaryId)}`, {
method: "DELETE",
});
await loadSummarySearch();
}
function renderLogDetail(item) {
const details = [];
if (item.source_id) details.push(`来源:${item.source_id}`);
@@ -711,6 +888,21 @@ accountRows.addEventListener("click", (event) => {
if (!button) return;
openEditAccountEditor(button.dataset.studentId);
});
teacherForm.addEventListener("submit", (event) => {
event.preventDefault();
loadTeachers();
});
teacherStatus.addEventListener("change", loadTeachers);
newTeacherBtn.addEventListener("click", openNewTeacherEditor);
cancelTeacherEditBtn.addEventListener("click", () => {
teacherEditor.hidden = true;
});
teacherEditForm.addEventListener("submit", saveTeacher);
teacherRows.addEventListener("click", (event) => {
const button = event.target.closest(".teacher-edit");
if (!button) return;
openEditTeacherEditor(button.dataset.teacherId);
});
reviewStatus.addEventListener("change", loadReviews);
reviewRows.addEventListener("click", (event) => {
const approve = event.target.closest(".review-approve");
@@ -750,8 +942,26 @@ summarySearchForm.addEventListener("submit", (event) => {
event.preventDefault();
loadSummarySearch();
});
summarySearchMissingTime.addEventListener("change", loadSummarySearch);
summarySearchRows.addEventListener("click", (event) => {
const button = event.target.closest(".summary-toggle");
const saveTime = event.target.closest(".summary-time-save");
const deleteSummary = event.target.closest(".summary-delete");
if (saveTime) {
const input = summarySearchRows.querySelector(`[data-summary-time-input='${saveTime.dataset.summaryId}']`);
const timeRange = input ? input.value.trim() : "";
if (!timeRange) {
alert("请输入时间段");
return;
}
updateCourseSummaryTime(saveTime.dataset.summaryId, timeRange).catch((error) => alert(error.message));
return;
}
if (deleteSummary) {
if (!confirm("确认删除这条课程小结?")) return;
deleteCourseSummary(deleteSummary.dataset.summaryId).catch((error) => alert(error.message));
return;
}
if (!button) return;
const target = document.getElementById(button.dataset.target);
if (!target) return;
+44 -7
View File
@@ -37,6 +37,7 @@ let currentRecords = [];
let recordSort = { date: "asc", time: "asc" };
let currentRecordOrder = [];
let correctedRecords = new Map();
let expandedSummaryRecords = new Set();
let activeCorrectionKey = "";
let activeDeleteKey = "";
@@ -63,7 +64,7 @@ function metric(label, value) {
}
function makeRecordKey(row, index) {
return JSON.stringify([index, row.date, row.time, row.student, row.duration, row.teacher, row.subject]);
return row.record_id || JSON.stringify([index, row.date, row.time, row.student, row.duration, row.teacher, row.subject]);
}
function buildRecordLine(row) {
@@ -182,6 +183,8 @@ function renderRecordRow(row) {
const correctedClass = corrected ? " corrected-row" : "";
const actionLabel = corrected ? "编辑" : "纠错";
const badge = corrected ? '<span class="correction-badge">已修改</span>' : "";
const summaryCount = Number(displayRow.summary_count || 0);
const summaryExpanded = expandedSummaryRecords.has(key);
return `<tr class="record-row${correctedClass}">
<td>${escapeHtml(displayRow.date)} ${escapeHtml(displayRow.weekday)}</td>
<td>${escapeHtml(displayRow.time)}</td>
@@ -191,6 +194,7 @@ function renderRecordRow(row) {
<td class="num">${escapeHtml(displayRow.duration)}</td>
<td class="record-action-cell">
<div class="record-actions">
<button class="small-button summary-toggle" type="button" data-record-key="${escapeHtml(key)}" ${summaryCount > 0 ? "" : "disabled"}>${summaryExpanded ? "收起小结" : "课程小结"}</button>
<button class="small-button correction-edit" type="button" data-record-key="${escapeHtml(key)}">${actionLabel}</button>
<button class="small-button record-delete" type="button" data-record-key="${escapeHtml(key)}">删除</button>
${badge}
@@ -199,6 +203,15 @@ function renderRecordRow(row) {
</tr>`;
}
function renderSummaryEntry(summary) {
const title = summary.title || "课程小结";
const time = summary.time_range ? ` · ${summary.time_range}` : "";
return `<div class="record-summary-item">
<div class="record-summary-title">${escapeHtml(title)}${escapeHtml(time)}</div>
<div class="summary-body">${escapeHtml(summary.body || "暂无正文")}</div>
</div>`;
}
function renderGroupedRecords(records) {
currentRecordOrder = [];
return groupRecordsByTeacher(records)
@@ -213,13 +226,28 @@ function renderGroupedRecords(records) {
</tr>${sortRecordsForDisplay(group.records)
.map((row) => {
currentRecordOrder.push(row._recordKey);
return renderRecordRow(row);
const summaryCount = Number(row.summary_count || 0);
const summaryRow = summaryCount
? `<tr class="summary-collapse-row" data-summary-row="${escapeHtml(row._recordKey)}" ${expandedSummaryRecords.has(row._recordKey) ? "" : "hidden"}>
<td colspan="7">${(row.summaries || []).map(renderSummaryEntry).join("")}</td>
</tr>`
: "";
return `${renderRecordRow(row)}${summaryRow}`;
})
.join("")}`,
)
.join("");
}
function toggleSummaryRow(key) {
if (expandedSummaryRecords.has(key)) {
expandedSummaryRecords.delete(key);
} else {
expandedSummaryRecords.add(key);
}
renderCurrentRecords();
}
function statusClass(status) {
if (status === "欠费") return "debt";
if (status === "预警") return "warning";
@@ -279,6 +307,7 @@ function updateCorrectionToolbar(message = "", isError = false) {
function resetCorrections() {
correctedRecords = new Map();
currentRecordOrder = [];
expandedSummaryRecords = new Set();
activeCorrectionKey = "";
updateCorrectionToolbar();
}
@@ -421,7 +450,7 @@ async function submitDeleteRecord() {
const data = await fetchJson("/api/deletions", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ items: [{ original_line: buildRecordLine(original) }] }),
body: JSON.stringify({ items: [{ record_id: original.record_id }] }),
});
closeDeleteDialog();
updateCorrectionToolbar(`已提交 ${data.submitted} 条删除审核`);
@@ -488,12 +517,15 @@ async function copyCorrectedRecords() {
async function submitCorrectedRecords() {
const items = currentRecordOrder
.map((key) => {
const original = findCurrentRecord(key);
const corrected = correctedRecords.get(key);
if (!original || !corrected) return null;
if (!corrected) return null;
return {
original_line: buildRecordLine(original),
corrected_line: buildRecordLine(corrected),
record_id: corrected.record_id,
date: corrected.date,
time: corrected.time,
student: corrected.student,
teacher: corrected.teacher,
subject: corrected.subject,
};
})
.filter(Boolean);
@@ -597,8 +629,13 @@ document.querySelectorAll("[data-query]").forEach((button) => {
});
recordRows.addEventListener("click", (event) => {
const summaryButton = event.target.closest(".summary-toggle");
const editButton = event.target.closest(".correction-edit");
const deleteButton = event.target.closest(".record-delete");
if (summaryButton) {
toggleSummaryRow(summaryButton.dataset.recordKey);
return;
}
if (editButton) {
openCorrectionDialog(editButton.dataset.recordKey);
return;
+2 -2
View File
@@ -4,7 +4,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>新时空教务管理系统</title>
<link rel="stylesheet" href="/static/styles.css?v=20260613-date-time-sort" />
<link rel="stylesheet" href="/static/styles.css?v=20260615-record-summary-toggle" />
</head>
<body>
<header class="topbar">
@@ -140,6 +140,6 @@
</section>
</div>
<script src="/static/app.js?v=20260615-delete-review"></script>
<script src="/static/app.js?v=20260615-record-summary-toggle"></script>
</body>
</html>
+44 -1
View File
@@ -211,7 +211,7 @@ h2 {
}
.summary-search-row {
grid-template-columns: minmax(180px, 1.4fr) repeat(5, minmax(110px, 0.8fr)) 88px;
grid-template-columns: minmax(180px, 1.4fr) repeat(6, minmax(100px, 0.8fr)) 88px;
}
input,
@@ -630,6 +630,49 @@ td {
color: var(--accent-strong);
}
.small-button:disabled {
border-color: #e4e7ec;
background: #f2f4f7;
color: #98a2b3;
cursor: not-allowed;
}
.small-button:disabled:hover {
border-color: #e4e7ec;
color: #98a2b3;
}
.summary-collapse-row td {
padding: 0;
background: #fbfcfd;
}
.record-summary-item {
padding: 12px 16px;
border-top: 1px solid var(--line);
}
.record-summary-title {
margin-bottom: 8px;
color: var(--accent-strong);
font-size: 13px;
font-weight: 700;
}
.summary-time-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
}
.summary-time-input {
width: 120px;
height: 32px;
padding: 0 8px;
font-size: 13px;
}
.correction-badge {
display: inline-flex;
align-items: center;