Add daily report calendar to the header
This commit is contained in:
1 parent
2a54838d59
commit
4356160cc9
11 files changed
+735
-28
No files matched your search
@@ -33,7 +33,11 @@
|
||||
|
||||
日报列表按业务日期展示,一天只展示一行。接口取数和XML上传遵循相同的覆盖规则:新版本成功生成并入库后,才替换该日有效日报并更新月报;新版本尚待核对或失败时,原有效日报仍可下载并继续计入月报。
|
||||
|
||||
点击列表中的日期,可查看该日概览;上方日期、房数和耗时始终对应当前查看日期。待处理新版本会单独提示,尚无有效日报时显示空值,不借用其他日期的成功结果。
|
||||
点击列表中的日期,可查看该日概览;上方日期与房数始终对应当前查看日期。待处理新版本会单独提示,尚无有效日报时显示空值,不借用其他日期的成功结果。
|
||||
|
||||
顶部概览提供“自动下载 ARR、上传 ARR.XML、ARRIVAL DATE、NO. OF ROOM、报表”五个区域,顶部不再展示处理耗时。报表日历默认显示当前业务月份,支持切换月份;每一天按是否存在当前有效日报标为“已生成”或“未生成”。历史版本、已删除版本和仅待核对的任务不算已生成;已有有效日报、另有待核对新版本时,仍标为已生成。读取中或读取失败会单独显示,不把未知状态当作未生成。
|
||||
|
||||
点击日历日期只切换查看日期和日报列表月份,不会获取Oracle数据、生成报表或提交人工核对值。需要下载新数据时,仍须在自动下载入口主动提交;未保存的人工价格继续受切换保护。日历月份与列表分页独立,显示整月状态;生成或删除后的刷新会更新标记。
|
||||
|
||||
“历史记录”保留同一天的旧版本和未完成版本。删除前页面会说明影响:
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
2026-10-09:日报按日期展示一个有效版本,支持查看历史、删除及月报同步;概览跟随所选日期。发布需要数据库022及配套月报worker,详见[交付说明](../deploy/OHIP_RELEASE_HANDOVER.md#日报版本管理升级2026-10-09)。
|
||||
|
||||
顶部新增“报表”月历,按当前有效日报显示已生成/未生成日期,可切换月份和选择日期查看;顶部处理耗时已移除。日历只读取ARR已保存状态,不查询Oracle或自动创建报表。
|
||||
|
||||
2026-09-18:[直接数据入口](DIRECT_DATA_ENTRY.md)已接通日期按钮、日报、复核和月报;独立本机模拟8875,未查询实际Oracle。
|
||||
|
||||
2026-09-17:[本机接口模拟全流程及按钮联调](LOCAL_API_SIMULATION.md)已在独立8874验证;[原文件重放](LOCAL_XML_REPLAY.md)8873保留。两个入口均明确未连接Oracle,不改变默认运行配置。
|
||||
|
||||
+227
-5
@@ -59,6 +59,16 @@
|
||||
dailyOverview: null,
|
||||
dailyOverviewTimer: null,
|
||||
dailyOverviewRequest: 0,
|
||||
reportDateSelectionRequest: 0,
|
||||
reportCalendarMonth: "",
|
||||
reportCalendarDataMonth: "",
|
||||
reportCalendarContext: "",
|
||||
reportCalendarRows: [],
|
||||
reportCalendarLoading: false,
|
||||
reportCalendarError: false,
|
||||
reportCalendarRequest: 0,
|
||||
reportCalendarFocusDate: "",
|
||||
reportCalendarInitialized: false,
|
||||
dailyHistoryDate: "",
|
||||
dailyHistoryItems: [],
|
||||
dailyHistoryTotal: 0,
|
||||
@@ -647,6 +657,7 @@
|
||||
}
|
||||
const storageKey = `arr:last-download:v2:${encodeURIComponent(config.context_id)}:${encodeURIComponent(state.arrDownloadUsername)}`;
|
||||
if (state.arrDownloadStorageKey !== storageKey) {
|
||||
state.reportDateSelectionRequest += 1;
|
||||
const contextChanged = state.arrDownloadContextId && state.arrDownloadContextId !== config.context_id;
|
||||
state.arrDownloadStorageKey = storageKey;
|
||||
state.arrDownloadContextId = config.context_id;
|
||||
@@ -669,6 +680,12 @@
|
||||
state.dailyHistoryDate = "";
|
||||
renderJobs();
|
||||
renderDailyHistory();
|
||||
state.reportCalendarRequest += 1;
|
||||
state.reportCalendarRows = [];
|
||||
state.reportCalendarDataMonth = "";
|
||||
state.reportCalendarError = false;
|
||||
state.reportCalendarLoading = true;
|
||||
loadReportCalendar();
|
||||
}
|
||||
state.manualReviewMode = "";
|
||||
state.dailyPriceReview = null;
|
||||
@@ -850,6 +867,7 @@
|
||||
}
|
||||
|
||||
function renderARRDataReview() {
|
||||
renderReportCalendar();
|
||||
const panel = $("#arr-data-review-panel");
|
||||
const review = state.arrDataReview;
|
||||
renderManualReviewWorkspace();
|
||||
@@ -1319,6 +1337,187 @@
|
||||
updateCompanyReportControls();
|
||||
}
|
||||
|
||||
function reportCalendarSelectionLocked() {
|
||||
return Boolean(arrDownloadActive() || state.arrDownloadBusy || state.arrDownloadLoading
|
||||
|| state.arrDataReviewLoading || state.arrDataReviewMutating || state.arrDataReviewFinalizing
|
||||
|| state.dailyPriceReviewLoading || state.dailyPriceReviewMutating
|
||||
|| state.uploadInFlight || state.dailyDeleting);
|
||||
}
|
||||
|
||||
function currentReportGenerated(row) {
|
||||
const job = row?.current_job;
|
||||
return Boolean(job && job.is_current === true && !job.is_retired
|
||||
&& ["succeeded", "accepted"].includes(job.status));
|
||||
}
|
||||
|
||||
function reportCalendarReady() {
|
||||
return !state.reportCalendarLoading && !state.reportCalendarError
|
||||
&& state.reportCalendarDataMonth === state.reportCalendarMonth
|
||||
&& state.reportCalendarContext === state.arrDownloadContextId;
|
||||
}
|
||||
|
||||
function renderReportCalendar(focusDay = false) {
|
||||
const month = state.reportCalendarMonth || localMonth();
|
||||
state.reportCalendarMonth = month;
|
||||
const first = new Date(`${month}-01T12:00:00Z`);
|
||||
const dayCount = new Date(Date.UTC(first.getUTCFullYear(), first.getUTCMonth() + 1, 0)).getUTCDate();
|
||||
const leading = (first.getUTCDay() + 6) % 7;
|
||||
const selected = state.dailyOverviewDate;
|
||||
const today = localDate();
|
||||
if (!validARRDate(state.reportCalendarFocusDate) || state.reportCalendarFocusDate.slice(0, 7) !== month) {
|
||||
state.reportCalendarFocusDate = selected?.slice(0, 7) === month ? selected
|
||||
: today.slice(0, 7) === month ? today : `${month}-01`;
|
||||
}
|
||||
const activeDay = document.activeElement?.closest?.("[data-report-calendar-day]");
|
||||
const restoreDayFocus = focusDay || Boolean(activeDay);
|
||||
const ready = reportCalendarReady();
|
||||
const locked = reportCalendarSelectionLocked();
|
||||
const format = (options) => new Intl.DateTimeFormat(I18N?.locale || "zh-CN", { timeZone: "UTC", calendar: "gregory", ...options });
|
||||
const dateLabel = format({ year: "numeric", month: "long", day: "numeric", weekday: "long" });
|
||||
const weekday = format({ weekday: "short" });
|
||||
const weekdays = [];
|
||||
for (let index = 0; index < 7; index += 1) {
|
||||
const monday = new Date("2026-09-07T12:00:00Z");
|
||||
monday.setUTCDate(monday.getUTCDate() + index);
|
||||
weekdays.push(`<span aria-hidden="true">${escapeHtml(weekday.format(monday))}</span>`);
|
||||
}
|
||||
const rows = new Map(state.reportCalendarRows.map((row) => [row.business_date, row]));
|
||||
const days = Array.from({ length: leading }, () => '<span class="report-calendar-spacer" aria-hidden="true"></span>');
|
||||
let generated = 0;
|
||||
for (let number = 1; number <= dayCount; number += 1) {
|
||||
const value = `${month}-${String(number).padStart(2, "0")}`;
|
||||
const exists = ready && currentReportGenerated(rows.get(value));
|
||||
if (exists) generated += 1;
|
||||
const status = !ready ? state.reportCalendarLoading ? "loading_day" : "unknown" : exists ? "generated" : "not_generated";
|
||||
const statusLabel = I18N.t(`report_calendar.${status}`);
|
||||
const shortLabel = I18N.t(`report_calendar.day_${status}`);
|
||||
const symbol = exists ? "✓" : ready ? "—" : "·";
|
||||
const selectedDay = value === selected;
|
||||
days.push(`<button type="button" class="report-calendar-day ${exists ? "is-generated" : ready ? "is-ungenerated" : "is-unknown"}${value === today ? " is-today" : ""}" data-report-calendar-day="${value}" aria-label="${escapeHtml(`${dateLabel.format(new Date(`${value}T12:00:00Z`))} · ${statusLabel}`)}" aria-pressed="${selectedDay}" ${value === today ? 'aria-current="date"' : ""} tabindex="${value === state.reportCalendarFocusDate ? 0 : -1}" ${locked ? "disabled" : ""}><span class="report-calendar-day-number">${number}</span><span class="report-calendar-day-state"><span aria-hidden="true">${symbol}</span><span>${escapeHtml(shortLabel)}</span></span></button>`);
|
||||
}
|
||||
$("#report-calendar-month").textContent = I18N.formatMonth(month);
|
||||
$("#report-calendar-previous").disabled = month === "2020-01";
|
||||
$("#report-calendar-next").disabled = month === "2099-12";
|
||||
$("#report-calendar-previous").setAttribute("aria-label", I18N.t("history.previous_month"));
|
||||
$("#report-calendar-next").setAttribute("aria-label", I18N.t("history.next_month"));
|
||||
$("#report-calendar-current").textContent = I18N.t("history.current_month");
|
||||
$("#report-calendar-weekdays").innerHTML = weekdays.join("");
|
||||
$("#report-calendar-days").innerHTML = days.join("");
|
||||
$("#report-calendar").setAttribute("aria-busy", String(state.reportCalendarLoading));
|
||||
$("#report-calendar-status").textContent = I18N.t(state.reportCalendarLoading ? "report_calendar.loading"
|
||||
: state.reportCalendarError ? "report_calendar.failed" : ready ? "report_calendar.coverage" : "report_calendar.loading",
|
||||
{ count: formatInteger(generated), total: formatInteger(dayCount) });
|
||||
$("#report-calendar-help").textContent = I18N.t(locked ? "report_calendar.locked" : "report_calendar.help");
|
||||
$("#report-calendar-retry").hidden = !state.reportCalendarError;
|
||||
$("#report-calendar-legend-generated").textContent = I18N.t("report_calendar.generated");
|
||||
$("#report-calendar-legend-empty").textContent = I18N.t("report_calendar.not_generated");
|
||||
if (restoreDayFocus) $(`[data-report-calendar-day="${state.reportCalendarFocusDate}"]`, $("#report-calendar-days"))?.focus();
|
||||
}
|
||||
|
||||
async function loadReportCalendar() {
|
||||
if (!state.reportCalendarInitialized) return;
|
||||
const month = state.reportCalendarMonth || localMonth();
|
||||
const context = state.arrDownloadContextId;
|
||||
const sequence = ++state.reportCalendarRequest;
|
||||
state.reportCalendarLoading = true;
|
||||
state.reportCalendarError = false;
|
||||
renderReportCalendar();
|
||||
const isCurrent = () => state.reportCalendarMonth === month
|
||||
&& state.arrDownloadContextId === context && state.reportCalendarRequest === sequence;
|
||||
try {
|
||||
const envelope = await api(`/api/daily-reports?month=${encodeURIComponent(month)}&limit=200&offset=0`, { returnEnvelope: true });
|
||||
if (!isCurrent()) return;
|
||||
const rows = envelope?.data;
|
||||
const total = Number(envelope?.pagination?.total);
|
||||
// Only a complete successful month snapshot can establish an ungenerated day.
|
||||
if (!Array.isArray(rows) || !Number.isInteger(total) || total !== rows.length || total > 31
|
||||
|| rows.some((row) => !validARRDate(row?.business_date) || row.business_date.slice(0, 7) !== month
|
||||
|| !Object.prototype.hasOwnProperty.call(row, "current_job"))
|
||||
|| new Set(rows.map((row) => row.business_date)).size !== rows.length) throw new Error("Incomplete calendar snapshot");
|
||||
state.reportCalendarRows = rows;
|
||||
state.reportCalendarDataMonth = month;
|
||||
state.reportCalendarContext = context;
|
||||
} catch (_) {
|
||||
if (!isCurrent()) return;
|
||||
state.reportCalendarError = true;
|
||||
state.reportCalendarRows = [];
|
||||
state.reportCalendarDataMonth = "";
|
||||
} finally {
|
||||
if (isCurrent()) {
|
||||
state.reportCalendarLoading = false;
|
||||
renderReportCalendar();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function changeReportCalendarMonth(month, focusDate = "") {
|
||||
if (!validMonth(month) || month < "2020-01" || month > "2099-12") return;
|
||||
state.reportCalendarMonth = month;
|
||||
state.reportCalendarFocusDate = focusDate;
|
||||
state.reportCalendarRequest += 1;
|
||||
state.reportCalendarRows = [];
|
||||
state.reportCalendarDataMonth = "";
|
||||
state.reportCalendarError = false;
|
||||
state.reportCalendarLoading = true;
|
||||
renderReportCalendar(Boolean(focusDate));
|
||||
await loadReportCalendar();
|
||||
}
|
||||
|
||||
async function selectReportCalendarDate(date) {
|
||||
if (!validARRDate(date) || date.slice(0, 7) !== state.reportCalendarMonth || reportCalendarSelectionLocked()) return false;
|
||||
const context = state.arrDownloadContextId;
|
||||
const month = state.reportCalendarMonth;
|
||||
const pending = selectDailyReportDate(date, { syncHistory: true });
|
||||
const sequence = state.reportDateSelectionRequest;
|
||||
const selected = await pending;
|
||||
if (selected && context === state.arrDownloadContextId && sequence === state.reportDateSelectionRequest
|
||||
&& month === state.reportCalendarMonth && state.dailyOverviewDate === date) {
|
||||
state.reportCalendarFocusDate = date;
|
||||
renderReportCalendar(true);
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
async function moveReportCalendarFocus(key, date) {
|
||||
if (!validARRDate(date) || reportCalendarSelectionLocked()) return;
|
||||
const day = new Date(`${date}T12:00:00Z`);
|
||||
if (["PageUp", "PageDown"].includes(key)) {
|
||||
const month = shiftMonth(date.slice(0, 7), key === "PageUp" ? -1 : 1);
|
||||
const last = new Date(`${month}-01T12:00:00Z`);
|
||||
last.setUTCMonth(last.getUTCMonth() + 1);
|
||||
last.setUTCDate(0);
|
||||
await changeReportCalendarMonth(month, `${month}-${String(Math.min(day.getUTCDate(), last.getUTCDate())).padStart(2, "0")}`);
|
||||
return;
|
||||
}
|
||||
const weekday = (day.getUTCDay() + 6) % 7;
|
||||
const deltas = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7, Home: -weekday, End: 6 - weekday };
|
||||
if (!Object.prototype.hasOwnProperty.call(deltas, key)) return;
|
||||
day.setUTCDate(day.getUTCDate() + deltas[key]);
|
||||
const value = day.toISOString().slice(0, 10);
|
||||
if (value.slice(0, 7) !== state.reportCalendarMonth) await changeReportCalendarMonth(value.slice(0, 7), value);
|
||||
else {
|
||||
state.reportCalendarFocusDate = value;
|
||||
renderReportCalendar(true);
|
||||
}
|
||||
}
|
||||
|
||||
function bindReportCalendar() {
|
||||
$("#report-calendar-previous").addEventListener("click", () => changeReportCalendarMonth(shiftMonth(state.reportCalendarMonth, -1)));
|
||||
$("#report-calendar-next").addEventListener("click", () => changeReportCalendarMonth(shiftMonth(state.reportCalendarMonth, 1)));
|
||||
$("#report-calendar-current").addEventListener("click", () => changeReportCalendarMonth(localMonth()));
|
||||
$("#report-calendar-retry").addEventListener("click", loadReportCalendar);
|
||||
$("#report-calendar-days").addEventListener("click", (event) => {
|
||||
const day = event.target.closest("[data-report-calendar-day]");
|
||||
if (day) selectReportCalendarDate(day.dataset.reportCalendarDay);
|
||||
});
|
||||
$("#report-calendar-days").addEventListener("keydown", (event) => {
|
||||
const day = event.target.closest("[data-report-calendar-day]");
|
||||
if (!day || !["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End", "PageUp", "PageDown"].includes(event.key)) return;
|
||||
event.preventDefault();
|
||||
moveReportCalendarFocus(event.key, day.dataset.reportCalendarDay);
|
||||
});
|
||||
}
|
||||
|
||||
function dailyJobSource(job) {
|
||||
return job?.filename === "ARR.json" ? I18N.t("arr_download.source_label") : job?.filename || "—";
|
||||
}
|
||||
@@ -1365,15 +1564,15 @@
|
||||
$("#metric-current-version").hidden = !current || (!pending && !failed);
|
||||
$("#metric-current-version").textContent = I18N.t("daily.current_preserved");
|
||||
const showResult = Boolean(current && !pending);
|
||||
$("#metric-duration").textContent = showResult ? duration(current) : "—";
|
||||
$("#metric-rooms").textContent = showResult ? formatInteger(current.no_of_rooms) : "—";
|
||||
$("#metric-duration-note").textContent = I18N.t(showResult ? "metric.duration_from_submit" : "daily.after_generation");
|
||||
$("#metric-rooms-note").textContent = I18N.t(showResult ? "metric.rooms" : "daily.after_generation");
|
||||
renderReportCalendar();
|
||||
}
|
||||
|
||||
function syncDailyOverviewDate(date) {
|
||||
if (!validARRDate(date)) {
|
||||
state.dailyOverviewDate = "";
|
||||
state.reportDateSelectionRequest += 1;
|
||||
state.dailyOverview = null;
|
||||
state.dailyOverviewRequest += 1;
|
||||
window.clearTimeout(state.dailyOverviewTimer);
|
||||
@@ -1381,6 +1580,7 @@
|
||||
return;
|
||||
}
|
||||
if (state.dailyOverviewDate !== date) {
|
||||
state.reportDateSelectionRequest += 1;
|
||||
state.dailyOverviewDate = date;
|
||||
state.dailyOverview = null;
|
||||
state.dailyOverviewRequest += 1;
|
||||
@@ -1406,15 +1606,28 @@
|
||||
} catch (_) { /* Do not substitute a different date's result when this day cannot be read. */ }
|
||||
}
|
||||
|
||||
async function selectDailyReportDate(date) {
|
||||
if (!validARRDate(date) || state.dailyDeleting || state.arrDownloadBusy || dailyPriceReviewHasUnsavedChanges()) {
|
||||
async function selectDailyReportDate(date, { syncHistory = false } = {}) {
|
||||
if (!validARRDate(date) || reportCalendarSelectionLocked() || dailyPriceReviewHasUnsavedChanges()) {
|
||||
if (dailyPriceReviewHasUnsavedChanges()) showToast(I18N.t("daily_review.save_before_switch"), true);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
$("#arr-download-date").value = date;
|
||||
state.arrDownloadDateEdited = true;
|
||||
renderARRDownload();
|
||||
const context = state.arrDownloadContextId;
|
||||
const sequence = ++state.reportDateSelectionRequest;
|
||||
const isCurrent = () => state.dailyOverviewDate === date && state.arrDownloadContextId === context
|
||||
&& state.reportDateSelectionRequest === sequence;
|
||||
if (!dailyOverviewRow()) await loadDailyOverview(date);
|
||||
if (!isCurrent()) return false;
|
||||
if (syncHistory && state.jobsMonth !== date.slice(0, 7)) {
|
||||
state.jobsMonth = date.slice(0, 7);
|
||||
state.jobsOffset = 0;
|
||||
renderHistoryMonthControl("jobs");
|
||||
await loadJobs(false, false);
|
||||
if (!isCurrent()) return false;
|
||||
}
|
||||
return isCurrent();
|
||||
}
|
||||
|
||||
function renderJobs() {
|
||||
@@ -1676,6 +1889,7 @@
|
||||
}
|
||||
|
||||
function renderDailyPriceReview(review = state.dailyPriceReview) {
|
||||
renderReportCalendar();
|
||||
const panel = $("#daily-price-review-panel");
|
||||
const body = $("#daily-price-review-body");
|
||||
const empty = $("#daily-price-review-empty");
|
||||
@@ -2261,6 +2475,7 @@
|
||||
);
|
||||
setTraceLiveState(I18N?.t("task.live_unselected") || "未选择任务");
|
||||
}
|
||||
await loadReportCalendar();
|
||||
} catch (error) {
|
||||
if (!isCurrent()) return;
|
||||
$("#jobs-body").innerHTML = `<tr><td class="empty-cell" colspan="7">${escapeHtml(I18N?.t("daily.unavailable") || "日报记录暂时无法读取")}</td></tr>`;
|
||||
@@ -2633,6 +2848,7 @@
|
||||
showToast(error.message, true);
|
||||
} finally {
|
||||
state.uploadInFlight = false;
|
||||
renderReportCalendar();
|
||||
fileInput.disabled = !state.health.processing_ready;
|
||||
dropzone.classList.toggle("is-disabled", !state.health.processing_ready);
|
||||
button.removeAttribute("aria-busy");
|
||||
@@ -3638,6 +3854,7 @@
|
||||
renderARRDownload();
|
||||
renderARRDataReview();
|
||||
renderARRCalendar();
|
||||
renderReportCalendar();
|
||||
renderJobs(state.jobs);
|
||||
renderDailyHistory();
|
||||
if (state.dailyDeleteRequest) {
|
||||
@@ -3724,6 +3941,7 @@
|
||||
function bindEvents() {
|
||||
bindHistoryMonthControls();
|
||||
bindARRCalendar();
|
||||
bindReportCalendar();
|
||||
$$("[data-tab]").forEach((button) => button.addEventListener("click", () => activateTab(button.dataset.tab)));
|
||||
$("#xml-file").addEventListener("change", (event) => {
|
||||
if (state.uploadInFlight) return;
|
||||
@@ -4016,6 +4234,8 @@
|
||||
async function boot() {
|
||||
$("#arr-download-date").value = defaultARRDate();
|
||||
$("#company-report-month").value = localMonth();
|
||||
state.reportCalendarMonth = localMonth();
|
||||
renderReportCalendar();
|
||||
populateMonths([]);
|
||||
bindEvents();
|
||||
activateTab(location.hash.slice(1) || "daily", false);
|
||||
@@ -4036,6 +4256,8 @@
|
||||
loadCompanyDraft(),
|
||||
loadCompanyReportHistory(location.hash.slice(1) === "usage"),
|
||||
]);
|
||||
state.reportCalendarInitialized = true;
|
||||
await loadReportCalendar();
|
||||
}
|
||||
|
||||
boot();
|
||||
|
||||
@@ -254,6 +254,24 @@
|
||||
"metric.duration": ["处理耗时", "Processing time", "เวลาประมวลผล"],
|
||||
"metric.duration_from_submit": ["从提交至完成", "From submission to completion", "ตั้งแต่ส่งจนเสร็จ"],
|
||||
"metric.rooms": ["保留记录的售出房数", "Sold rooms in retained records", "จำนวนห้องที่ขายในรายการที่เก็บไว้"],
|
||||
"report_calendar.title": ["报表", "Reports", "รายงาน"],
|
||||
"report_calendar.widget_aria": ["报表日历", "Report calendar", "ปฏิทินรายงาน"],
|
||||
"report_calendar.generated": ["已生成", "Generated", "สร้างแล้ว"],
|
||||
"report_calendar.not_generated": ["未生成", "Not generated", "ยังไม่สร้าง"],
|
||||
"report_calendar.loading_day": ["读取中", "Loading", "กำลังอ่าน"],
|
||||
"report_calendar.unknown": ["未确认", "Not confirmed", "ยังไม่ยืนยัน"],
|
||||
"report_calendar.day_generated": ["已生成", "Ready", "มีแล้ว"],
|
||||
"report_calendar.day_not_generated": ["未生成", "None", "ยังไม่มี"],
|
||||
"report_calendar.day_loading_day": ["读取中", "Loading", "กำลังอ่าน"],
|
||||
"report_calendar.day_unknown": ["未确认", "Unknown", "ไม่ทราบ"],
|
||||
"report_calendar.loading": ["正在读取本月报表状态…", "Loading report status for this month…", "กำลังอ่านสถานะรายงานของเดือนนี้…"],
|
||||
"report_calendar.failed": ["状态读取失败,暂不能确认哪些日期已生成。", "Status could not be read. Generated dates are not confirmed.", "อ่านสถานะไม่สำเร็จ ยังยืนยันวันที่สร้างรายงานแล้วไม่ได้"],
|
||||
"report_calendar.coverage": ["本月已生成 {count} / {total} 天", "Reports generated for {count} of {total} days", "สร้างรายงานแล้ว {count} / {total} วัน"],
|
||||
"report_calendar.help": ["选择日期查看日报;不会自动下载或处理。", "Select a date to view its report. This does not fetch or process data.", "เลือกวันที่เพื่อดูรายงาน จะไม่ดึงหรือประมวลผลข้อมูลอัตโนมัติ"],
|
||||
"report_calendar.locked": ["任务进行中,结束后可切换查看日期。", "A task is in progress. Change the viewed date after it finishes.", "งานกำลังดำเนินการ เปลี่ยนวันที่ดูได้เมื่อเสร็จแล้ว"],
|
||||
"report_calendar.day_group": ["选择日期查看日报", "Choose a report date", "เลือกวันที่รายงาน"],
|
||||
"report_calendar.legend": ["日报生成状态", "Daily report generation status", "สถานะการสร้างรายงานรายวัน"],
|
||||
"report_calendar.retry": ["重新读取", "Read again", "อ่านอีกครั้ง"],
|
||||
"daily.current": ["当前有效日报", "Current daily report", "รายงานรายวันที่ใช้อยู่"],
|
||||
"daily.no_current": ["无当前有效日报", "No current daily report", "ไม่มีรายงานรายวันที่ใช้อยู่"],
|
||||
"daily.history_only": ["仅保留历史版本", "Historical versions only", "มีเฉพาะรุ่นในประวัติ"],
|
||||
|
||||
@@ -121,22 +121,38 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<article class="metric-card">
|
||||
<article class="metric-card arrival-metric">
|
||||
<span>ARRIVAL DATE</span>
|
||||
<strong id="metric-arrival">—</strong>
|
||||
<small id="metric-arrival-note">当前查看日期</small>
|
||||
<small id="metric-current-version" hidden>当前有效日报仍保留</small>
|
||||
</article>
|
||||
<article class="metric-card">
|
||||
<span>处理耗时</span>
|
||||
<strong id="metric-duration">—</strong>
|
||||
<small id="metric-duration-note">从提交至完成</small>
|
||||
</article>
|
||||
<article class="metric-card accent-card">
|
||||
<span>NO. OF ROOM</span>
|
||||
<strong id="metric-rooms">—</strong>
|
||||
<small id="metric-rooms-note">保留记录的售出房数</small>
|
||||
</article>
|
||||
|
||||
<section class="panel report-calendar" id="report-calendar" aria-label="报表日历" aria-describedby="report-calendar-help">
|
||||
<header class="report-calendar-heading">
|
||||
<h2 id="report-calendar-title">报表</h2>
|
||||
<button id="report-calendar-current" type="button">本月</button>
|
||||
</header>
|
||||
<div class="report-calendar-navigation">
|
||||
<button id="report-calendar-previous" type="button" aria-label="上一个月">‹</button>
|
||||
<strong id="report-calendar-month"></strong>
|
||||
<button id="report-calendar-next" type="button" aria-label="下一个月">›</button>
|
||||
</div>
|
||||
<div class="report-calendar-weekdays" id="report-calendar-weekdays" aria-hidden="true"></div>
|
||||
<div class="report-calendar-days" id="report-calendar-days" role="group" aria-label="选择日期查看日报"></div>
|
||||
<div class="report-calendar-legend" aria-label="日报生成状态">
|
||||
<span><b aria-hidden="true">✓</b><span id="report-calendar-legend-generated">已生成</span></span>
|
||||
<span><b aria-hidden="true">—</b><span id="report-calendar-legend-empty">未生成</span></span>
|
||||
</div>
|
||||
<p class="report-calendar-status" id="report-calendar-status" role="status" aria-live="polite">正在读取本月报表状态…</p>
|
||||
<p class="report-calendar-help" id="report-calendar-help">选择日期查看日报;不会自动下载或处理。</p>
|
||||
<button class="report-calendar-retry" id="report-calendar-retry" type="button" hidden>重新读取</button>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section class="panel arr-download-pending" id="arr-download-pending" aria-labelledby="arr-download-pending-title" hidden>
|
||||
|
||||
@@ -158,7 +158,7 @@ button { color: inherit; }
|
||||
.panel-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; padding: 22px 24px 0; }
|
||||
.panel-heading h2 { margin: 4px 0 0; font-size: 18px; line-height: 1.3; letter-spacing: -.025em; }
|
||||
|
||||
.daily-overview { display: grid; grid-template-columns: minmax(240px, 1.25fr) minmax(280px, 1.45fr) repeat(3, minmax(0, .8fr)); gap: 16px; align-items: stretch; margin-bottom: 20px; }
|
||||
.daily-overview { display: grid; grid-template-columns: minmax(220px, 1.1fr) minmax(230px, 1.15fr) minmax(150px, .75fr) minmax(110px, .55fr) minmax(360px, 1.8fr); gap: 16px; align-items: stretch; margin-bottom: 20px; }
|
||||
.daily-overview > * { min-width: 0; }
|
||||
.daily-overview > .upload-panel { display: flex; flex-direction: column; }
|
||||
.daily-overview .upload-footer { margin-top: auto; }
|
||||
@@ -272,6 +272,36 @@ button { color: inherit; }
|
||||
.upload-panel { min-height: 0; overflow: hidden; border-color: #dbe3ef; box-shadow: 0 1px 2px rgba(16, 24, 40, .04), 0 18px 44px rgba(31, 51, 82, .07); }
|
||||
.daily-overview > .metric-card { align-self: stretch; display: flex; flex-direction: column; justify-content: center; height: auto; min-height: 0; padding: 24px 22px; }
|
||||
.daily-overview > .metric-card::after { right: -24px; bottom: -30px; width: 82px; height: 82px; border-width: 12px; }
|
||||
.daily-overview > .arrival-metric { padding-left: 16px; padding-right: 16px; }
|
||||
.daily-overview > .arrival-metric strong { font-size: clamp(20px, 1.5vw, 24px); }
|
||||
.report-calendar { padding: 14px 12px; overflow: hidden; }
|
||||
.report-calendar-heading { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
||||
.report-calendar-heading h2 { margin: 0; font-size: 17px; letter-spacing: -.02em; }
|
||||
.report-calendar button { min-height: 44px; border: 1px solid var(--line); border-radius: 8px; color: var(--ink); background: #fff; cursor: pointer; }
|
||||
.report-calendar-heading button { padding: 0 10px; color: var(--blue); font-size: 12px; }
|
||||
.report-calendar-navigation { display: grid; grid-template-columns: 44px minmax(0, 1fr) 44px; align-items: center; gap: 8px; margin: 8px 0; }
|
||||
.report-calendar-navigation strong { text-align: center; font-size: 13px; font-weight: 700; }
|
||||
.report-calendar-navigation button { padding: 0; font-size: 22px; }
|
||||
.report-calendar-weekdays, .report-calendar-days { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 2px; }
|
||||
.report-calendar-weekdays { margin-bottom: 5px; color: var(--muted); text-align: center; font-size: 10px; }
|
||||
.report-calendar-days .report-calendar-day { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; min-width: 0; padding: 3px 0; border-color: transparent; background: var(--surface-soft); font-variant-numeric: tabular-nums; }
|
||||
.report-calendar-day-number { font-size: 12px; font-weight: 650; line-height: 1.3; }
|
||||
.report-calendar-day-state { display: flex; align-items: center; gap: 1px; font-size: 9px; line-height: 1.4; white-space: nowrap; }
|
||||
.report-calendar-days .is-generated { color: var(--blue-dark); background: var(--blue-soft); }
|
||||
.report-calendar-days .is-ungenerated { color: var(--muted); }
|
||||
.report-calendar-days .is-unknown { color: var(--muted); border-style: dashed; border-color: var(--line); }
|
||||
.report-calendar-days .is-today { border-color: #a3b9e8; }
|
||||
.report-calendar-days [aria-pressed="true"] { border-color: var(--blue); box-shadow: inset 0 0 0 1px var(--blue); color: var(--blue-dark); }
|
||||
.report-calendar button:hover:not(:disabled) { border-color: var(--blue); background: var(--blue-soft); }
|
||||
.report-calendar button:focus-visible { position: relative; z-index: 1; outline: 3px solid rgba(37, 99, 235, .45); outline-offset: 1px; }
|
||||
.report-calendar button:disabled { cursor: not-allowed; opacity: .55; }
|
||||
.report-calendar-legend { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; margin-top: 11px; color: var(--muted); font-size: 10px; }
|
||||
.report-calendar-legend > span { display: inline-flex; align-items: center; gap: 4px; }
|
||||
.report-calendar-legend > span:first-child b { color: var(--blue-dark); }
|
||||
.report-calendar-status, .report-calendar-help { margin: 6px 0 0; color: var(--muted); font-size: 10px; line-height: 1.6; }
|
||||
.report-calendar-status { color: var(--blue-dark); }
|
||||
.report-calendar-help { color: var(--subtle); }
|
||||
.report-calendar .report-calendar-retry { margin-top: 8px; padding: 0 10px; color: var(--blue); font-size: 12px; }
|
||||
.upload-heading { align-items: center; padding: 24px 28px 0; }
|
||||
.upload-heading h2 { margin-top: 0; font-size: 20px; letter-spacing: -.03em; }
|
||||
.process-panel { min-height: 640px; overflow: hidden; }
|
||||
@@ -767,10 +797,11 @@ input:focus, select:focus { border-color: var(--blue); box-shadow: 0 0 0 3px rgb
|
||||
|
||||
:focus-visible { outline: 3px solid rgba(37, 99, 235, .35); outline-offset: 3px; }
|
||||
|
||||
@media (min-width: 701px) and (max-width: 1199px) {
|
||||
.daily-overview { grid-template-columns: repeat(6, minmax(0, 1fr)); }
|
||||
.daily-overview > .arr-download-panel, .daily-overview > .upload-panel { grid-column: span 3; }
|
||||
.daily-overview > .metric-card { grid-column: span 2; }
|
||||
@media (min-width: 701px) and (max-width: 1399px) {
|
||||
.daily-overview { grid-template-columns: repeat(12, minmax(0, 1fr)); }
|
||||
.daily-overview > .arr-download-panel, .daily-overview > .upload-panel { grid-column: span 6; }
|
||||
.daily-overview > .metric-card { grid-column: span 3; }
|
||||
.daily-overview > .report-calendar { grid-column: span 6; }
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
|
||||
Reference in new issue
Block a user