Compare commits
2
Commits
dfd7e5685d
...
10d5d70dd0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10d5d70dd0 | ||
|
|
7104ac9c39 |
@@ -1,7 +1,7 @@
|
||||
# Graph Report - /home/agent/ai-agent/backend/routes/../workspace/73f46153-e8cc-447a-82ad-2eeefa00689f (2026-04-12)
|
||||
|
||||
## Corpus Check
|
||||
- 43 files · ~183,959 words
|
||||
- 43 files · ~210,827 words
|
||||
- Verdict: corpus is large enough that graph structure adds value.
|
||||
|
||||
## Summary
|
||||
|
||||
+162
-5
@@ -512,12 +512,12 @@ const trustStats = [
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Map footer info -->
|
||||
<!-- Map footer info - Dynamic business hours status -->
|
||||
<div class="px-5 py-4 flex items-center justify-between border-t border-secondary-100 dark:border-secondary-700">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-2 h-2 bg-emerald-400 rounded-full animate-pulse"></div>
|
||||
<span class="text-xs text-secondary-600 dark:text-secondary-300 font-medium">Open now</span>
|
||||
<span class="text-xs text-secondary-400 dark:text-secondary-500">· Closes at 6 PM</span>
|
||||
<div class="flex items-center gap-2" id="business-status" role="status" aria-live="polite" aria-atomic="true">
|
||||
<div id="status-dot" class="w-2 h-2 rounded-full"></div>
|
||||
<span id="status-label" class="text-xs font-medium"></span>
|
||||
<span id="status-detail" class="text-xs"></span>
|
||||
</div>
|
||||
<a
|
||||
href="https://maps.app.goo.gl/9bZ5ez1pjbU74ATq5"
|
||||
@@ -1024,3 +1024,160 @@ const trustStats = [
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Business Hours Status Utility
|
||||
* Automatically detects user timezone and shows Open/Closed status
|
||||
* Business hours: Monday–Friday, 10:00 AM – 6:00 PM IST (Asia/Kolkata)
|
||||
*/
|
||||
const BUSINESS_TIMEZONE = 'Asia/Kolkata';
|
||||
const OPEN_HOUR = 10; // 10:00 AM
|
||||
const CLOSE_HOUR = 18; // 6:00 PM
|
||||
|
||||
interface BusinessStatus {
|
||||
isOpen: boolean;
|
||||
label: string;
|
||||
detail: string;
|
||||
}
|
||||
|
||||
function getBusinessStatus(): BusinessStatus {
|
||||
const now = new Date();
|
||||
|
||||
// Get current day and time in the business timezone (IST)
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
timeZone: BUSINESS_TIMEZONE,
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
weekday: 'short',
|
||||
hour12: false,
|
||||
};
|
||||
|
||||
const parts = new Intl.DateTimeFormat('en-US', options).formatToParts(now);
|
||||
const weekday = parts.find(p => p.type === 'weekday')?.value ?? '';
|
||||
const hour = parseInt(parts.find(p => p.type === 'hour')?.value ?? '0', 10);
|
||||
const minute = parseInt(parts.find(p => p.type === 'minute')?.value ?? '0', 10);
|
||||
|
||||
// Map weekday abbreviation to day number (Mon=1 ... Fri=5)
|
||||
const dayMap: Record<string, number> = {
|
||||
Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6,
|
||||
};
|
||||
const dayNum = dayMap[weekday] ?? 0;
|
||||
|
||||
const isWeekday = dayNum >= 1 && dayNum <= 5;
|
||||
const currentMinutes = hour * 60 + minute;
|
||||
const openMinutes = OPEN_HOUR * 60;
|
||||
const closeMinutes = CLOSE_HOUR * 60;
|
||||
const isWithinHours = currentMinutes >= openMinutes && currentMinutes < closeMinutes;
|
||||
const isOpen = isWeekday && isWithinHours;
|
||||
|
||||
let detail = '';
|
||||
if (isOpen) {
|
||||
// Show closing time in user's local timezone
|
||||
const closingIST = new Date(now);
|
||||
// Calculate the closing time in IST, then convert
|
||||
const istOffset = getTimezoneOffsetMinutes(BUSINESS_TIMEZONE, now);
|
||||
const localOffset = now.getTimezoneOffset(); // negative for east of UTC
|
||||
const diffMinutes = istOffset + localOffset;
|
||||
const minutesUntilClose = closeMinutes - currentMinutes;
|
||||
|
||||
if (minutesUntilClose <= 60) {
|
||||
detail = `· Closes in ${minutesUntilClose} min`;
|
||||
} else {
|
||||
// Format closing time in user's local time
|
||||
const closingLocal = new Date(now.getTime() + (minutesUntilClose - diffMinutes + diffMinutes) * 60000);
|
||||
// Simpler approach: calculate closing time in UTC then display locally
|
||||
const nowInIST = currentMinutes;
|
||||
const minsToClose = closeMinutes - nowInIST;
|
||||
const closingTime = new Date(now.getTime() + minsToClose * 60000);
|
||||
const localCloseStr = closingTime.toLocaleTimeString(undefined, {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
hour12: true,
|
||||
});
|
||||
detail = `· Closes at ${localCloseStr}`;
|
||||
}
|
||||
} else {
|
||||
// Calculate next opening time
|
||||
let daysUntilOpen = 0;
|
||||
let nextDay = dayNum;
|
||||
|
||||
if (isWeekday && currentMinutes < openMinutes) {
|
||||
// Today but before opening
|
||||
daysUntilOpen = 0;
|
||||
} else {
|
||||
// After hours or weekend — find next weekday
|
||||
nextDay = (dayNum + 1) % 7;
|
||||
daysUntilOpen = 1;
|
||||
while (nextDay < 1 || nextDay > 5) {
|
||||
nextDay = (nextDay + 1) % 7;
|
||||
daysUntilOpen++;
|
||||
}
|
||||
}
|
||||
|
||||
if (daysUntilOpen === 0) {
|
||||
const minsToOpen = openMinutes - currentMinutes;
|
||||
if (minsToOpen <= 60) {
|
||||
detail = `· Opens in ${minsToOpen} min`;
|
||||
} else {
|
||||
const openingTime = new Date(now.getTime() + minsToOpen * 60000);
|
||||
const localOpenStr = openingTime.toLocaleTimeString(undefined, {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
hour12: true,
|
||||
});
|
||||
detail = `· Opens at ${localOpenStr}`;
|
||||
}
|
||||
} else if (daysUntilOpen === 1) {
|
||||
detail = '· Opens tomorrow at 10 AM IST';
|
||||
} else {
|
||||
const dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
||||
detail = `· Opens ${dayNames[nextDay]} at 10 AM IST`;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
label: isOpen ? 'Open' : 'Closed',
|
||||
detail,
|
||||
};
|
||||
}
|
||||
|
||||
/** Get timezone offset in minutes from UTC for a given IANA timezone */
|
||||
function getTimezoneOffsetMinutes(tz: string, date: Date): number {
|
||||
const utcStr = date.toLocaleString('en-US', { timeZone: 'UTC' });
|
||||
const tzStr = date.toLocaleString('en-US', { timeZone: tz });
|
||||
const utcDate = new Date(utcStr);
|
||||
const tzDate = new Date(tzStr);
|
||||
return (tzDate.getTime() - utcDate.getTime()) / 60000;
|
||||
}
|
||||
|
||||
function updateBusinessStatus(): void {
|
||||
const dot = document.getElementById('status-dot');
|
||||
const label = document.getElementById('status-label');
|
||||
const detailEl = document.getElementById('status-detail');
|
||||
|
||||
if (!dot || !label || !detailEl) return;
|
||||
|
||||
const status = getBusinessStatus();
|
||||
|
||||
if (status.isOpen) {
|
||||
dot.className = 'w-2 h-2 rounded-full bg-emerald-400 animate-pulse';
|
||||
label.className = 'text-xs font-medium text-emerald-600 dark:text-emerald-400';
|
||||
label.textContent = 'Open';
|
||||
} else {
|
||||
dot.className = 'w-2 h-2 rounded-full bg-red-400';
|
||||
label.className = 'text-xs font-medium text-red-600 dark:text-red-400';
|
||||
label.textContent = 'Closed';
|
||||
}
|
||||
|
||||
detailEl.textContent = status.detail;
|
||||
detailEl.className = 'text-xs text-secondary-400 dark:text-secondary-500';
|
||||
}
|
||||
|
||||
// Run on page load
|
||||
updateBusinessStatus();
|
||||
|
||||
// Re-check every 60 seconds so status updates if user keeps the page open
|
||||
setInterval(updateBusinessStatus, 60_000);
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user