fix: align analysis defaults to latest completed month
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -1546,10 +1546,10 @@
|
|||||||
if (isNaN(d.getTime())) return "";
|
if (isNaN(d.getTime())) return "";
|
||||||
return `${d.getFullYear()}-${("0" + (d.getMonth() + 1)).slice(-2)}-${("0" + d.getDate()).slice(-2)}`;
|
return `${d.getFullYear()}-${("0" + (d.getMonth() + 1)).slice(-2)}-${("0" + d.getDate()).slice(-2)}`;
|
||||||
};
|
};
|
||||||
const calculateTargetHours = (startStr, endStr) => {
|
const calculateTargetHours = (startStr, endStr) => {
|
||||||
const start = new Date(startStr);
|
const start = new Date(startStr);
|
||||||
const end = new Date(endStr);
|
const end = new Date(endStr);
|
||||||
if (isNaN(start.getTime()) || isNaN(end.getTime()) || end < start) return 0;
|
if (isNaN(start.getTime()) || isNaN(end.getTime()) || end < start) return 0;
|
||||||
const startUtc = Date.UTC(start.getFullYear(), start.getMonth(), start.getDate());
|
const startUtc = Date.UTC(start.getFullYear(), start.getMonth(), start.getDate());
|
||||||
const endUtc = Date.UTC(end.getFullYear(), end.getMonth(), end.getDate());
|
const endUtc = Date.UTC(end.getFullYear(), end.getMonth(), end.getDate());
|
||||||
const oneDay = 86400000;
|
const oneDay = 86400000;
|
||||||
@@ -1561,13 +1561,37 @@
|
|||||||
const isWeekend = dayOfWeek === 0 || dayOfWeek === 6;
|
const isWeekend = dayOfWeek === 0 || dayOfWeek === 6;
|
||||||
const isHoliday = isWeekend || HOLIDAYS.includes(dateText);
|
const isHoliday = isWeekend || HOLIDAYS.includes(dateText);
|
||||||
totalTarget += isHoliday ? HOLIDAY_TARGET_HOURS : WEEKDAY_TARGET_HOURS;
|
totalTarget += isHoliday ? HOLIDAY_TARGET_HOURS : WEEKDAY_TARGET_HOURS;
|
||||||
}
|
}
|
||||||
return Math.round(totalTarget);
|
return Math.round(totalTarget);
|
||||||
};
|
};
|
||||||
const findHeaderIndex = (normalizedHeaders, candidates) => {
|
const formatLocalDate = (date) => {
|
||||||
const targets = candidates.map(normalizeHeader);
|
if (!(date instanceof Date) || isNaN(date.getTime())) return '';
|
||||||
for (let i = 0; i < normalizedHeaders.length; i++) {
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
||||||
if (targets.includes(normalizedHeaders[i])) return i;
|
};
|
||||||
|
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 targets = candidates.map(normalizeHeader);
|
||||||
|
for (let i = 0; i < normalizedHeaders.length; i++) {
|
||||||
|
if (targets.includes(normalizedHeaders[i])) return i;
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
};
|
};
|
||||||
@@ -2501,11 +2525,12 @@
|
|||||||
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;
|
||||||
syncScopeButtons();
|
document.getElementById('end-date').value = defaultRange.endDate;
|
||||||
refreshScopedSelections(false);
|
syncScopeButtons();
|
||||||
render();
|
refreshScopedSelections(false);
|
||||||
|
render();
|
||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2688,17 +2713,18 @@
|
|||||||
if (mainSearch) mainSearch.value = '';
|
if (mainSearch) mainSearch.value = '';
|
||||||
if (searchDropdown) searchDropdown.classList.add('hidden');
|
if (searchDropdown) searchDropdown.classList.add('hidden');
|
||||||
|
|
||||||
refreshScopedSelections(false);
|
refreshScopedSelections(false);
|
||||||
if (personSelect) personSelect.value = '';
|
if (personSelect) personSelect.value = '';
|
||||||
|
|
||||||
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
document.getElementById('team-select').addEventListener('change', () => { updateFilters(); render(); });
|
document.getElementById('team-select').addEventListener('change', () => { updateFilters(); render(); });
|
||||||
|
|||||||
Reference in New Issue
Block a user