Compare commits
2
Commits
| 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)
|
# Graph Report - /home/agent/ai-agent/backend/routes/../workspace/73f46153-e8cc-447a-82ad-2eeefa00689f (2026-04-12)
|
||||||
|
|
||||||
## Corpus Check
|
## Corpus Check
|
||||||
- 43 files · ~183,959 words
|
- 43 files · ~210,827 words
|
||||||
- Verdict: corpus is large enough that graph structure adds value.
|
- Verdict: corpus is large enough that graph structure adds value.
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|||||||
+162
-5
@@ -512,12 +512,12 @@ const trustStats = [
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</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="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="flex items-center gap-2" id="business-status" role="status" aria-live="polite" aria-atomic="true">
|
||||||
<div class="w-2 h-2 bg-emerald-400 rounded-full animate-pulse"></div>
|
<div id="status-dot" class="w-2 h-2 rounded-full"></div>
|
||||||
<span class="text-xs text-secondary-600 dark:text-secondary-300 font-medium">Open now</span>
|
<span id="status-label" class="text-xs font-medium"></span>
|
||||||
<span class="text-xs text-secondary-400 dark:text-secondary-500">· Closes at 6 PM</span>
|
<span id="status-detail" class="text-xs"></span>
|
||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
href="https://maps.app.goo.gl/9bZ5ez1pjbU74ATq5"
|
href="https://maps.app.goo.gl/9bZ5ez1pjbU74ATq5"
|
||||||
@@ -1024,3 +1024,160 @@ const trustStats = [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</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