fix: align analysis defaults to latest completed month

This commit is contained in:
hyunho
2026-03-30 10:27:21 +09:00
parent e67fd41cbf
commit 8121c9cf41
2 changed files with 111 additions and 33 deletions

View File

@@ -153,9 +153,9 @@ const seatMapState = {
let currentView = "project"; let currentView = "project";
const globalDateState = { const globalDateState = {
loaded: true, loaded: false,
startDate: "2026-01-01", startDate: "",
endDate: "2026-01-31", endDate: "",
}; };
const organizationHistoryState = { const organizationHistoryState = {
@@ -194,6 +194,58 @@ function getTodayDate() {
return `${now.getFullYear()}-${padDatePart(now.getMonth() + 1)}-${padDatePart(now.getDate())}`; return `${now.getFullYear()}-${padDatePart(now.getMonth() + 1)}-${padDatePart(now.getDate())}`;
} }
function parseDateOnly(value) {
const raw = String(value || "").trim();
if (!raw) return null;
const match = raw.match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (!match) return null;
const year = Number(match[1]);
const monthIndex = Number(match[2]) - 1;
const day = Number(match[3]);
const parsed = new Date(year, monthIndex, day);
if (
Number.isNaN(parsed.getTime())
|| parsed.getFullYear() !== year
|| parsed.getMonth() !== monthIndex
|| parsed.getDate() !== day
) {
return null;
}
return parsed;
}
function formatDateOnly(date) {
if (!(date instanceof Date) || Number.isNaN(date.getTime())) return "";
return `${date.getFullYear()}-${padDatePart(date.getMonth() + 1)}-${padDatePart(date.getDate())}`;
}
function getMonthRangeForDate(date) {
if (!(date instanceof Date) || Number.isNaN(date.getTime())) {
return { startDate: "", endDate: "" };
}
const start = new Date(date.getFullYear(), date.getMonth(), 1);
const end = new Date(date.getFullYear(), date.getMonth() + 1, 0);
return {
startDate: formatDateOnly(start),
endDate: formatDateOnly(end),
};
}
function getPreviousMonthRange(baseDate = new Date()) {
return getMonthRangeForDate(new Date(baseDate.getFullYear(), baseDate.getMonth() - 1, 1));
}
function resolveLatestCompletedMonthRange(maxDateText) {
const fallbackRange = getPreviousMonthRange();
const latestAvailableDate = parseDateOnly(String(maxDateText || "").slice(0, 10));
if (!latestAvailableDate) return fallbackRange;
const fallbackStart = parseDateOnly(fallbackRange.startDate);
if (fallbackStart && latestAvailableDate >= fallbackStart) {
return fallbackRange;
}
return getMonthRangeForDate(latestAvailableDate);
}
function syncOrganizationHistoryControls() { function syncOrganizationHistoryControls() {
if (!organizationHistoryControls) return; if (!organizationHistoryControls) return;
const visible = currentView === "organization"; const visible = currentView === "organization";
@@ -324,10 +376,10 @@ async function ensureGlobalDateRangeLoaded() {
const payload = await fetchJson("/api/integration/summary"); const payload = await fetchJson("/api/integration/summary");
const work = payload?.date_ranges?.work || {}; const work = payload?.date_ranges?.work || {};
const voucher = payload?.date_ranges?.voucher || {}; const voucher = payload?.date_ranges?.voucher || {};
const starts = [work.min_work_date, voucher.min_voucher_date].filter(Boolean).sort();
const ends = [work.max_work_date, voucher.max_voucher_date].filter(Boolean).sort(); const ends = [work.max_work_date, voucher.max_voucher_date].filter(Boolean).sort();
globalDateState.startDate = starts[0] ? String(starts[0]).slice(0, 10) : ""; const defaultRange = resolveLatestCompletedMonthRange(ends.length ? ends[ends.length - 1] : "");
globalDateState.endDate = ends.length ? String(ends[ends.length - 1]).slice(0, 10) : ""; globalDateState.startDate = defaultRange.startDate;
globalDateState.endDate = defaultRange.endDate;
globalDateState.loaded = true; globalDateState.loaded = true;
syncGlobalDateControlInputs(); syncGlobalDateControlInputs();
postGlobalDateRangeToFrame(projectFrame); postGlobalDateRangeToFrame(projectFrame);

View File

@@ -1564,6 +1564,30 @@
} }
return Math.round(totalTarget); return Math.round(totalTarget);
}; };
const formatLocalDate = (date) => {
if (!(date instanceof Date) || isNaN(date.getTime())) return '';
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
};
const getMonthRangeForDate = (date) => {
if (!(date instanceof Date) || isNaN(date.getTime())) return { startDate: '', endDate: '' };
const start = new Date(date.getFullYear(), date.getMonth(), 1);
const end = new Date(date.getFullYear(), date.getMonth() + 1, 0);
return {
startDate: formatLocalDate(start),
endDate: formatLocalDate(end)
};
};
const getDefaultMhDateRange = (dates = []) => {
const now = new Date();
const previousMonthRange = getMonthRangeForDate(new Date(now.getFullYear(), now.getMonth() - 1, 1));
const latestDateText = [...dates].filter(Boolean).sort().slice(-1)[0] || '';
const latestDate = latestDateText ? new Date(latestDateText) : null;
const previousMonthStart = new Date(now.getFullYear(), now.getMonth() - 1, 1);
if (latestDate instanceof Date && !isNaN(latestDate.getTime()) && latestDate < previousMonthStart) {
return getMonthRangeForDate(latestDate);
}
return previousMonthRange;
};
const findHeaderIndex = (normalizedHeaders, candidates) => { const findHeaderIndex = (normalizedHeaders, candidates) => {
const targets = candidates.map(normalizeHeader); const targets = candidates.map(normalizeHeader);
for (let i = 0; i < normalizedHeaders.length; i++) { for (let i = 0; i < normalizedHeaders.length; i++) {
@@ -2501,8 +2525,9 @@
function initTeamTabAfterUpload() { function initTeamTabAfterUpload() {
if (teamData.length < 2) return; if (teamData.length < 2) return;
const dates = teamData.slice(1).map(r => dStr(r[columnMap.date])).filter(Boolean).sort(); const dates = teamData.slice(1).map(r => dStr(r[columnMap.date])).filter(Boolean).sort();
document.getElementById('start-date').value = dates[0]; const defaultRange = getDefaultMhDateRange(dates);
document.getElementById('end-date').value = dates[dates.length - 1]; document.getElementById('start-date').value = defaultRange.startDate;
document.getElementById('end-date').value = defaultRange.endDate;
syncScopeButtons(); syncScopeButtons();
refreshScopedSelections(false); refreshScopedSelections(false);
render(); render();
@@ -2693,8 +2718,9 @@
const dates = teamData.slice(1).map(r => dStr(r[columnMap.date])).filter(Boolean).sort(); const dates = teamData.slice(1).map(r => dStr(r[columnMap.date])).filter(Boolean).sort();
if (dates.length > 0) { if (dates.length > 0) {
startInput.value = dates[0]; const defaultRange = getDefaultMhDateRange(dates);
endInput.value = dates[dates.length - 1]; startInput.value = defaultRange.startDate;
endInput.value = defaultRange.endDate;
} }
render(); render();