indas-ui 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/auth.d.ts +1 -0
  2. package/dist/auth.js +5 -0
  3. package/dist/cjs/auth.js +10 -0
  4. package/dist/cjs/components/chat/chat.js +2 -2
  5. package/dist/cjs/components/dashboard/FilterBar.js +31 -8
  6. package/dist/cjs/components/dashboard/time-range.js +71 -27
  7. package/dist/cjs/components/layout/sidebar.js +17 -2
  8. package/dist/cjs/components/messaging/messaging-panel-content.js +2 -2
  9. package/dist/cjs/components/ui/navigation/tabs.js +4 -1
  10. package/dist/cjs/contexts/EmailContext.js +3 -2
  11. package/dist/cjs/contexts/MessagingContext.js +2 -2
  12. package/dist/cjs/contexts/NavigationContext.js +17 -2
  13. package/dist/cjs/contexts/NotificationContext.js +2 -2
  14. package/dist/cjs/contexts/SessionAdapterContext.js +8 -0
  15. package/dist/cjs/hooks/useApproval.js +2 -2
  16. package/dist/cjs/hooks/useBusinessAudit.js +2 -2
  17. package/dist/cjs/hooks/useDraft.js +2 -2
  18. package/dist/cjs/lib/api/utility/menu.js +8 -7
  19. package/dist/cjs/providers.js +3 -3
  20. package/dist/components/auth/index.js +0 -1
  21. package/dist/components/chat/chat.js +2 -2
  22. package/dist/components/chat/index.js +0 -1
  23. package/dist/components/dashboard/FilterBar.d.ts +5 -1
  24. package/dist/components/dashboard/FilterBar.js +31 -8
  25. package/dist/components/dashboard/charts/index.js +0 -1
  26. package/dist/components/dashboard/index.js +0 -1
  27. package/dist/components/dashboard/kpi/index.js +0 -1
  28. package/dist/components/dashboard/time-range.d.ts +12 -1
  29. package/dist/components/dashboard/time-range.js +71 -27
  30. package/dist/components/datagrid/cells/index.js +0 -1
  31. package/dist/components/datagrid/controls/menu/grouping/index.js +0 -1
  32. package/dist/components/datagrid/controls/menu/index.js +0 -1
  33. package/dist/components/datagrid/index.js +0 -1
  34. package/dist/components/forms/date-picker/index.js +0 -1
  35. package/dist/components/forms/dropdown/index.js +0 -1
  36. package/dist/components/forms/file-attachment/index.js +0 -1
  37. package/dist/components/forms/index.js +0 -1
  38. package/dist/components/forms/remark-input/index.js +0 -1
  39. package/dist/components/index.js +0 -1
  40. package/dist/components/layout/index.js +0 -1
  41. package/dist/components/layout/sidebar.js +17 -2
  42. package/dist/components/master/editable-cells/index.js +0 -1
  43. package/dist/components/master/index.js +0 -1
  44. package/dist/components/messaging/messaging-panel-content.js +2 -2
  45. package/dist/components/modals/index.js +0 -1
  46. package/dist/components/ui/feedback/badge.d.ts +1 -1
  47. package/dist/components/ui/index.js +0 -1
  48. package/dist/components/ui/navigation/tabs.js +4 -1
  49. package/dist/contexts/EmailContext.js +3 -2
  50. package/dist/contexts/MessagingContext.js +2 -2
  51. package/dist/contexts/NavigationContext.js +17 -2
  52. package/dist/contexts/NotificationContext.js +2 -2
  53. package/dist/contexts/SessionAdapterContext.d.ts +5 -0
  54. package/dist/contexts/SessionAdapterContext.js +7 -0
  55. package/dist/hooks/index.js +0 -1
  56. package/dist/hooks/useApproval.js +2 -2
  57. package/dist/hooks/useBusinessAudit.js +2 -2
  58. package/dist/hooks/useDraft.js +2 -2
  59. package/dist/index.js +0 -1
  60. package/dist/lib/api/activity/audit/index.js +0 -1
  61. package/dist/lib/api/activity/email/index.js +0 -1
  62. package/dist/lib/api/activity/index.js +0 -1
  63. package/dist/lib/api/activity/notifications/index.js +0 -1
  64. package/dist/lib/api/activity/remarks/index.js +0 -1
  65. package/dist/lib/api/approval/index.js +0 -1
  66. package/dist/lib/api/auth/index.js +0 -1
  67. package/dist/lib/api/core/index.js +0 -1
  68. package/dist/lib/api/draft/index.js +0 -1
  69. package/dist/lib/api/enquiry/index.js +0 -1
  70. package/dist/lib/api/estimation/index.js +0 -1
  71. package/dist/lib/api/index.js +0 -1
  72. package/dist/lib/api/master/index.js +0 -1
  73. package/dist/lib/api/utility/index.js +0 -1
  74. package/dist/lib/api/utility/menu.d.ts +7 -3
  75. package/dist/lib/api/utility/menu.js +8 -7
  76. package/dist/providers.d.ts +0 -1
  77. package/dist/providers.js +2 -2
  78. package/package.json +20 -5
package/dist/auth.d.ts ADDED
@@ -0,0 +1 @@
1
+ export { AuthSessionProvider } from './components/providers/session-provider.js';
package/dist/auth.js ADDED
@@ -0,0 +1,5 @@
1
+ // Auth entry: `import { AuthSessionProvider } from 'indas-ui/auth'`
2
+ // The one entry that needs next-auth (an optional peer dependency): it feeds next-auth's session into
3
+ // the library's session adapter, which every other component reads. Apps on another sign-in system
4
+ // skip this entry and use SessionAdapterProvider from 'indas-ui/providers' instead.
5
+ export { AuthSessionProvider } from './components/providers/session-provider.js';
@@ -0,0 +1,10 @@
1
+ 'use client';
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.AuthSessionProvider = void 0;
5
+ // Auth entry: `import { AuthSessionProvider } from 'indas-ui/auth'`
6
+ // The one entry that needs next-auth (an optional peer dependency): it feeds next-auth's session into
7
+ // the library's session adapter, which every other component reads. Apps on another sign-in system
8
+ // skip this entry and use SessionAdapterProvider from 'indas-ui/providers' instead.
9
+ var session_provider_1 = require("./components/providers/session-provider.js");
10
+ Object.defineProperty(exports, "AuthSessionProvider", { enumerable: true, get: function () { return session_provider_1.AuthSessionProvider; } });
@@ -8,7 +8,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
8
8
  * Domain-specific branding (logo, title, messages) is injected via props.
9
9
  */
10
10
  const react_1 = require("react");
11
- const react_2 = require("next-auth/react");
11
+ const SessionAdapterContext_1 = require("../../contexts/SessionAdapterContext.js");
12
12
  const navigation_1 = require("next/navigation");
13
13
  const lucide_react_1 = require("lucide-react");
14
14
  const utils_1 = require("../../lib/utils.js");
@@ -65,7 +65,7 @@ function bucketForDate(iso, now = new Date()) {
65
65
  return { id: `m-${d.getFullYear()}-${d.getMonth()}`, label: monthLabel, sort };
66
66
  }
67
67
  function Chat({ mode = 'fullpage', externalMuted, title = 'Chat', sidebarTitle, bubbleLogo, logo, MessagesComponent, fullpagePath, onBeforeEditMessage, initialMessage, initialConversationId, headerActions, onMessagesChange }) {
68
- const { data: session } = (0, react_2.useSession)();
68
+ const session = (0, SessionAdapterContext_1.useOptionalSessionAdapter)()?.data ?? null;
69
69
  const router = (0, navigation_1.useRouter)();
70
70
  const alerts = (0, GlobalAlertContext_1.useGlobalAlert)();
71
71
  const { t } = (0, LanguageContext_1.useLanguage)();
@@ -220,7 +220,7 @@ const fmtFiscalDay = (d) => `${String(d.getDate()).padStart(2, '0')} ${SHORT_MON
220
220
  // Fiscal-year-aware dropdown options. FY runs 1 Apr (fyStart) → 31 Mar (fyStart+1).
221
221
  // Weeks are W1..W52/53 counting 7-day spans from 1 Apr; months ordered Apr→Mar;
222
222
  // quarters Q1 Apr-Jun … Q4 Jan-Mar; year shown as "YYYY-YYYY".
223
- function buildTimeOptions(fyStart) {
223
+ function buildTimeOptions(fyStart, allTime = false) {
224
224
  const fyOpen = new Date(fyStart, 3, 1);
225
225
  const fyClose = new Date(fyStart + 1, 2, 31);
226
226
  // Monday-aligned weeks clamped to the FY: W1 = 1 Apr → first Sunday (partial),
@@ -248,7 +248,10 @@ function buildTimeOptions(fyStart) {
248
248
  { value: 'Q3', label: `Q3 (Oct – Dec ${fyStart})` },
249
249
  { value: 'Q4', label: `Q4 (Jan – Mar ${fyStart + 1})` },
250
250
  ];
251
- const years = [{ value: `${fyStart}`, label: `${fyStart}-${fyStart + 1}` }];
251
+ // This financial year and the two before it, newest first; "All Time" last where the host offers it.
252
+ const years = [0, 1, 2].map(n => ({ value: `${fyStart - n}`, label: `FY ${fyStart - n}-${String(fyStart - n + 1).slice(2)}` }));
253
+ if (allTime)
254
+ years.push({ value: 'all', label: 'All Time' });
252
255
  return [
253
256
  { value: 'Day', label: 'Day', dropdownOptions: DAY_OPTIONS, quickOptions: DAY_QUICK },
254
257
  { value: 'Week', label: 'Week', dropdownOptions: weeks, quickOptions: WEEK_QUICK },
@@ -324,11 +327,31 @@ function ForecastControl({ forecast, onForecastChange, timeFilter, onTimeFilterC
324
327
  ? 'text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))]'
325
328
  : 'text-[rgb(var(--fg-muted))] hover:bg-[rgb(var(--bg-subtle))] hover:border-[rgb(var(--color-primary))] hover:text-[rgb(var(--color-primary))]'), title: t('Forecast'), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.TrendingUp, { className: "w-4 h-4" }), forecast.enabled && !open && ((0, jsx_runtime_1.jsx)("span", { className: "absolute -top-1 -right-1 w-2 h-2 bg-[rgb(var(--color-primary))] rounded-full" }))] }), open && ((0, jsx_runtime_1.jsxs)("div", { className: (0, utils_1.cn)('absolute top-full right-0 mt-2 z-50 w-96 max-w-[calc(100vw-2rem)]', 'bg-[rgb(var(--bg-surface))] rounded-xl shadow-xl border border-[rgb(var(--bd-default))]', 'overflow-hidden'), children: [(0, jsx_runtime_1.jsxs)("div", { className: "px-3 py-2 border-b border-[rgb(var(--bd-default))] flex items-center justify-between gap-2", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2 min-w-0", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-xs font-semibold text-[rgb(var(--fg-default))] shrink-0", children: t('Forecast') }), onTimeFilterChange ? ((0, jsx_runtime_1.jsx)(dropdown_1.Dropdown, { options: TIME_FILTER_OPTIONS.map(o => ({ value: o.value, label: t(o.label) })), value: timeFilter, onValueChange: (v) => onTimeFilterChange(v), searchable: false, size: "sm", triggerClassName: "h-6 text-[11px] min-w-[6.5rem]" })) : ((0, jsx_runtime_1.jsxs)("span", { className: "text-[10px] text-[rgb(var(--fg-muted))]", children: ["\u00B7 ", t(timeFilter)] }))] }), (0, jsx_runtime_1.jsx)(ui_1.Switch, { checked: forecast.enabled, onCheckedChange: (enabled) => onForecastChange({ ...forecast, enabled }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: (0, utils_1.cn)('p-3 space-y-3', !forecast.enabled && 'opacity-50 pointer-events-none'), children: [(0, jsx_runtime_1.jsx)(tabs_1.Tabs, { tabs: exports.FORECAST_METHODS.map(m => ({ id: m.value, label: t(m.label) })), activeTab: forecast.method, onTabChange: (tabId) => onForecastChange({ ...forecast, method: tabId }), variant: "rounded", size: "sm", fullWidth: true }), activeMethod && ((0, jsx_runtime_1.jsx)("p", { className: "text-[10px] text-[rgb(var(--fg-muted))]", children: t(activeMethod.hint) })), (0, jsx_runtime_1.jsxs)("div", { className: "grid grid-cols-2 gap-3", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { className: "text-[10px] font-semibold text-[rgb(var(--fg-muted))] uppercase tracking-wider mb-1 block", children: t('Based On Last') }), (0, jsx_runtime_1.jsx)(Stepper, { value: forecast.period, min: limits.min, max: limits.max, onChange: setPeriod }), (0, jsx_runtime_1.jsx)("p", { className: "mt-1 text-[10px] text-[rgb(var(--fg-muted))] truncate", children: resolvePeriodRange(timeFilter, forecast.period, -1) })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { className: "text-[10px] font-semibold text-[rgb(var(--fg-muted))] uppercase tracking-wider mb-1 block", children: t('Forecast Ahead') }), (0, jsx_runtime_1.jsx)(Stepper, { value: forecast.horizon, min: horizonLimits.min, max: horizonLimits.max, onChange: setHorizon }), (0, jsx_runtime_1.jsx)("p", { className: "mt-1 text-[10px] text-[rgb(var(--fg-muted))] truncate", children: resolvePeriodRange(timeFilter, forecast.horizon, 1) })] })] })] })] }))] }));
326
329
  }
327
- function FilterBar({ title, contextLabel, secondaryToggle, timeFilter = 'Week', onTimeFilterChange, timeFilterSelection, onTimeFilterSelectionChange, hideTimeFilter = false, filters = [], filterValues = {}, onFilterChange, dateFrom, dateTo, onDateFromChange, onDateToChange, onApply, onReset, reports = [], onReportSelect, onExport, hideFilterButton = false, hideDateRange = false, showApplyButton = false, forecast, onForecastChange, onShare, showInsights = false, insightsActive = false, onInsightsClick, children, className, layout = 'default', fiscalStartYear }) {
330
+ function FilterBar({ title, contextLabel, description, secondaryToggle, timeFilter = 'Week', onTimeFilterChange, timeFilterSelection, onTimeFilterSelectionChange, hideTimeFilter = false, filters = [], filterValues = {}, onFilterChange, dateFrom, dateTo, onDateFromChange, onDateToChange, onApply, onReset, reports = [], onReportSelect, onExport, hideFilterButton = false, hideDateRange = false, showApplyButton = false, forecast, onForecastChange, onShare, showInsights = false, insightsActive = false, onInsightsClick, children, className, layout = 'default', fiscalStartYear, allTime = false, }) {
328
331
  const [popoverOpen, setPopoverOpen] = (0, react_1.useState)(false);
329
332
  const popoverRef = (0, react_1.useRef)(null);
330
333
  const buttonRef = (0, react_1.useRef)(null);
331
- const timeOptions = (0, react_1.useMemo)(() => (fiscalStartYear ? buildTimeOptions(fiscalStartYear) : TIME_FILTER_OPTIONS), [fiscalStartYear]);
334
+ // The divider between the secondary toggle and the time tabs only makes sense while they share a
335
+ // line. When the row wraps (narrow screens, long toggle labels), it would be left hanging at the
336
+ // end of the first line, so it hides.
337
+ const toggleRef = (0, react_1.useRef)(null);
338
+ const timeTabsRef = (0, react_1.useRef)(null);
339
+ const [tabsWrapped, setTabsWrapped] = (0, react_1.useState)(false);
340
+ (0, react_1.useEffect)(() => {
341
+ const a = toggleRef.current;
342
+ const b = timeTabsRef.current;
343
+ if (!a || !b || typeof ResizeObserver === 'undefined')
344
+ return;
345
+ const check = () => setTabsWrapped(a.offsetTop !== b.offsetTop);
346
+ const ro = new ResizeObserver(check);
347
+ ro.observe(a);
348
+ ro.observe(b);
349
+ if (a.parentElement)
350
+ ro.observe(a.parentElement);
351
+ check();
352
+ return () => ro.disconnect();
353
+ }, [secondaryToggle, hideTimeFilter]);
354
+ const timeOptions = (0, react_1.useMemo)(() => (fiscalStartYear ? buildTimeOptions(fiscalStartYear, allTime) : TIME_FILTER_OPTIONS), [fiscalStartYear, allTime]);
332
355
  // Handle click outside. Ignore clicks landing inside a portalled dropdown/select
333
356
  // list (rendered to document.body, outside popoverRef) so selecting a filter
334
357
  // option doesn't close this popover before the value commits.
@@ -391,10 +414,10 @@ function FilterBar({ title, contextLabel, secondaryToggle, timeFilter = 'Week',
391
414
  ...(filter === 'Year' && { years: selectedValues }),
392
415
  };
393
416
  onTimeFilterSelectionChange?.(newSelection);
394
- } }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex-1 text-center", children: [(0, jsx_runtime_1.jsx)("h1", { className: "text-sm font-semibold text-[rgb(var(--fg-default))]", children: title }), contextLabel && ((0, jsx_runtime_1.jsx)("span", { className: "text-xs text-[rgb(var(--fg-muted))]", children: contextLabel }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [!hideFilterButton && ((0, jsx_runtime_1.jsxs)("div", { className: "relative", children: [(0, jsx_runtime_1.jsxs)("button", { ref: buttonRef, onClick: () => setPopoverOpen(!popoverOpen), className: (0, utils_1.cn)('w-8 h-8 rounded-md flex items-center justify-center transition-colors relative', 'border border-[rgb(var(--bd-default))] bg-[rgb(var(--bg-surface))]', 'text-[rgb(var(--fg-muted))] hover:bg-[rgb(var(--bg-subtle))] hover:border-[rgb(var(--color-primary))] hover:text-[rgb(var(--color-primary))]', popoverOpen && 'bg-[rgb(var(--color-primary))] text-white border-[rgb(var(--color-primary))] hover:bg-[rgb(var(--color-primary))]'), title: "Filters", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ListFilter, { className: "w-4 h-4" }), hasActiveFilters && !popoverOpen && ((0, jsx_runtime_1.jsx)("span", { className: "absolute -top-1 -right-1 w-2 h-2 bg-[rgb(var(--color-primary))] rounded-full" }))] }), popoverOpen && ((0, jsx_runtime_1.jsxs)("div", { ref: popoverRef, className: (0, utils_1.cn)('absolute top-full right-0 mt-2 z-50', 'w-[95vw] sm:w-auto', 'bg-[rgb(var(--bg-surface))] rounded-xl shadow-xl border border-[rgb(var(--bd-default))]', 'overflow-hidden'), children: [(0, jsx_runtime_1.jsxs)("div", { className: "px-3 py-2 border-b border-[rgb(var(--bd-default))] flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-xs font-semibold text-[rgb(var(--fg-default))]", children: "Filters" }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setPopoverOpen(false), className: "w-5 h-5 rounded flex items-center justify-center hover:bg-[rgb(var(--bg-subtle))] transition-colors", children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { className: "w-3 h-3 text-[rgb(var(--fg-muted))]" }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "p-3 space-y-3", children: [filters.length > 0 && ((0, jsx_runtime_1.jsx)("div", { className: "grid gap-3", style: { gridTemplateColumns: `repeat(${filters.length}, minmax(140px, 1fr))` }, children: filters.map((filter) => ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { className: "text-[10px] font-semibold text-[rgb(var(--fg-muted))] uppercase tracking-wider mb-1 block", children: filter.label }), (0, jsx_runtime_1.jsx)(dropdown_1.Dropdown, { options: filter.options, multiSelect: filter.type === 'multiselect', value: filterValues[filter.id] ?? (filter.type === 'multiselect' ? [] : ''), onValueChange: (value) => onFilterChange?.(filter.id, value), placeholder: filter.placeholder || `All ${filter.label}s`, searchable: true, className: "w-full" })] }, filter.id))) })), children, !hideDateRange && ((0, jsx_runtime_1.jsx)(date_picker_1.DatePicker, { mode: "range", value: dateRangeValue, onChange: handleDateRangeChange, inline: true }))] })] }))] })), forecast && onForecastChange && ((0, jsx_runtime_1.jsx)(ForecastControl, { forecast: forecast, onForecastChange: onForecastChange, timeFilter: timeFilter, onTimeFilterChange: onTimeFilterChange })), showApplyButton && ((0, jsx_runtime_1.jsx)("button", { onClick: onApply, className: (0, utils_1.cn)('h-8 px-4 rounded-md flex items-center gap-1.5', 'bg-[rgb(var(--color-primary))] text-white', 'text-xs font-semibold', 'shadow-sm hover:shadow-md transition-all', 'hover:brightness-110'), children: "Apply" })), (reports.length > 0 || onShare || onExport) && ((0, jsx_runtime_1.jsx)(OverflowMenu, { reports: reports, onReportSelect: onReportSelect, onShare: onShare, onExport: onExport }))] })] }) }) }));
417
+ } }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex-1 text-center", children: [(0, jsx_runtime_1.jsx)("h1", { className: "text-sm font-semibold text-[rgb(var(--fg-default))]", children: title }), contextLabel && ((0, jsx_runtime_1.jsx)("span", { className: "text-xs text-[rgb(var(--fg-muted))]", children: contextLabel })), description && (0, jsx_runtime_1.jsx)("p", { className: "mt-0.5 text-xs text-[rgb(var(--fg-muted))]", children: description })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [!hideFilterButton && ((0, jsx_runtime_1.jsxs)("div", { className: "relative", children: [(0, jsx_runtime_1.jsxs)("button", { ref: buttonRef, onClick: () => setPopoverOpen(!popoverOpen), className: (0, utils_1.cn)('w-8 h-8 rounded-md flex items-center justify-center transition-colors relative', 'border border-[rgb(var(--bd-default))] bg-[rgb(var(--bg-surface))]', 'text-[rgb(var(--fg-muted))] hover:bg-[rgb(var(--bg-subtle))] hover:border-[rgb(var(--color-primary))] hover:text-[rgb(var(--color-primary))]', popoverOpen && 'bg-[rgb(var(--color-primary))] text-white border-[rgb(var(--color-primary))] hover:bg-[rgb(var(--color-primary))]'), title: "Filters", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ListFilter, { className: "w-4 h-4" }), hasActiveFilters && !popoverOpen && ((0, jsx_runtime_1.jsx)("span", { className: "absolute -top-1 -right-1 w-2 h-2 bg-[rgb(var(--color-primary))] rounded-full" }))] }), popoverOpen && ((0, jsx_runtime_1.jsxs)("div", { ref: popoverRef, className: (0, utils_1.cn)('absolute top-full right-0 mt-2 z-50', 'w-[95vw] sm:w-auto', 'bg-[rgb(var(--bg-surface))] rounded-xl shadow-xl border border-[rgb(var(--bd-default))]', 'overflow-hidden'), children: [(0, jsx_runtime_1.jsxs)("div", { className: "px-3 py-2 border-b border-[rgb(var(--bd-default))] flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-xs font-semibold text-[rgb(var(--fg-default))]", children: "Filters" }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setPopoverOpen(false), className: "w-5 h-5 rounded flex items-center justify-center hover:bg-[rgb(var(--bg-subtle))] transition-colors", children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { className: "w-3 h-3 text-[rgb(var(--fg-muted))]" }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "p-3 space-y-3", children: [filters.length > 0 && ((0, jsx_runtime_1.jsx)("div", { className: "grid gap-3", style: { gridTemplateColumns: `repeat(${filters.length}, minmax(140px, 1fr))` }, children: filters.map((filter) => ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { className: "text-[10px] font-semibold text-[rgb(var(--fg-muted))] uppercase tracking-wider mb-1 block", children: filter.label }), (0, jsx_runtime_1.jsx)(dropdown_1.Dropdown, { options: filter.options, multiSelect: filter.type === 'multiselect', value: filterValues[filter.id] ?? (filter.type === 'multiselect' ? [] : ''), onValueChange: (value) => onFilterChange?.(filter.id, value), placeholder: filter.placeholder || `All ${filter.label}s`, searchable: true, className: "w-full" })] }, filter.id))) })), children, !hideDateRange && ((0, jsx_runtime_1.jsx)(date_picker_1.DatePicker, { mode: "range", value: dateRangeValue, onChange: handleDateRangeChange, inline: true }))] })] }))] })), forecast && onForecastChange && ((0, jsx_runtime_1.jsx)(ForecastControl, { forecast: forecast, onForecastChange: onForecastChange, timeFilter: timeFilter, onTimeFilterChange: onTimeFilterChange })), showApplyButton && ((0, jsx_runtime_1.jsx)("button", { onClick: onApply, className: (0, utils_1.cn)('h-8 px-4 rounded-md flex items-center gap-1.5', 'bg-[rgb(var(--color-primary))] text-white', 'text-xs font-semibold', 'shadow-sm hover:shadow-md transition-all', 'hover:brightness-110'), children: "Apply" })), (reports.length > 0 || onShare || onExport) && ((0, jsx_runtime_1.jsx)(OverflowMenu, { reports: reports, onReportSelect: onReportSelect, onShare: onShare, onExport: onExport }))] })] }) }) }));
395
418
  }
396
419
  // Default layout: Title centered, controls on right
397
- return ((0, jsx_runtime_1.jsxs)("header", { className: (0, utils_1.cn)('bg-[rgb(var(--bg-surface))] border-b border-[rgb(var(--bd-default))]', 'px-4 py-3 lg:px-6', className), children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex-1 min-w-0 flex items-center gap-2.5", children: [(0, jsx_runtime_1.jsx)("h1", { className: "text-base sm:text-xl font-bold text-[rgb(var(--fg-default))] leading-tight truncate", children: title }), contextLabel && ((0, jsx_runtime_1.jsxs)("span", { className: "hidden sm:inline-flex items-center gap-1.5 shrink-0 h-6 px-2.5 rounded-full border border-[rgb(var(--bd-default))] bg-[rgb(var(--bg-subtle))] text-xs font-medium text-[rgb(var(--fg-muted))]", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.CalendarDays, { className: "w-3.5 h-3.5" }), contextLabel] }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [secondaryToggle && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: "hidden sm:block", children: (0, jsx_runtime_1.jsx)(tabs_1.Tabs, { tabs: secondaryToggle.options.map(opt => ({ id: opt.value, label: opt.label })), activeTab: secondaryToggle.value, onTabChange: secondaryToggle.onChange, size: "sm" }) }), (0, jsx_runtime_1.jsx)("div", { className: "hidden sm:block h-4 w-px bg-[rgb(var(--bd-default))] self-center" })] })), !hideTimeFilter && ((0, jsx_runtime_1.jsx)("div", { className: "hidden sm:block", children: (0, jsx_runtime_1.jsx)(tabs_1.Tabs, { tabs: timeOptions.map(opt => ({ id: opt.value, label: opt.label, dropdownOptions: opt.dropdownOptions, quickOptions: opt.quickOptions })), activeTab: timeFilter, onTabChange: (tabId) => onTimeFilterChange?.(tabId), variant: "rounded", size: "sm", dropdownValues: {
420
+ return ((0, jsx_runtime_1.jsxs)("header", { className: (0, utils_1.cn)('bg-[rgb(var(--bg-surface))] border-b border-[rgb(var(--bd-default))]', 'px-4 py-3 lg:px-6', className), children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-2", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex-auto min-w-0 flex items-center gap-2.5", children: [(0, jsx_runtime_1.jsx)("h1", { className: "text-base sm:text-xl font-bold text-[rgb(var(--fg-default))] leading-tight truncate", children: title }), contextLabel && ((0, jsx_runtime_1.jsxs)("span", { className: "hidden sm:inline-flex items-center gap-1.5 shrink-0 h-6 px-2.5 rounded-full border border-[rgb(var(--bd-default))] bg-[rgb(var(--bg-subtle))] text-xs font-medium text-[rgb(var(--fg-muted))]", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.CalendarDays, { className: "w-3.5 h-3.5" }), contextLabel] }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "ml-auto flex flex-wrap items-center justify-end gap-x-3 gap-y-2", children: [secondaryToggle && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { ref: toggleRef, className: "hidden sm:block", children: (0, jsx_runtime_1.jsx)(tabs_1.Tabs, { tabs: secondaryToggle.options.map(opt => ({ id: opt.value, label: opt.label })), activeTab: secondaryToggle.value, onTabChange: secondaryToggle.onChange, size: "sm" }) }), (0, jsx_runtime_1.jsx)("div", { className: (0, utils_1.cn)('hidden h-4 w-px bg-[rgb(var(--bd-default))] self-center', !tabsWrapped && 'sm:block') })] })), !hideTimeFilter && ((0, jsx_runtime_1.jsx)("div", { ref: timeTabsRef, className: "hidden sm:block", children: (0, jsx_runtime_1.jsx)(tabs_1.Tabs, { tabs: timeOptions.map(opt => ({ id: opt.value, label: opt.label, dropdownOptions: opt.dropdownOptions, quickOptions: opt.quickOptions })), activeTab: timeFilter, onTabChange: (tabId) => onTimeFilterChange?.(tabId), variant: "rounded", size: "sm", dropdownValues: {
398
421
  Day: timeFilterSelection?.days || [],
399
422
  Week: timeFilterSelection?.weeks || [],
400
423
  Month: timeFilterSelection?.months || [],
@@ -413,7 +436,7 @@ function FilterBar({ title, contextLabel, secondaryToggle, timeFilter = 'Week',
413
436
  onTimeFilterSelectionChange?.(newSelection);
414
437
  } }) })), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-0.5 rounded-lg border border-[rgb(var(--bd-default))] bg-[rgb(var(--bg-surface))] p-0.5", children: [!hideFilterButton && ((0, jsx_runtime_1.jsxs)("div", { className: "relative", children: [(0, jsx_runtime_1.jsxs)("button", { ref: buttonRef, onClick: () => setPopoverOpen(!popoverOpen), className: (0, utils_1.cn)('w-7 h-7 rounded-md flex items-center justify-center transition-colors relative', popoverOpen
415
438
  ? 'bg-[rgb(var(--color-primary))] text-white shadow-sm'
416
- : 'text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))]'), title: "Filters", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ListFilter, { className: "w-4 h-4" }), hasActiveFilters && !popoverOpen && ((0, jsx_runtime_1.jsx)("span", { className: "absolute -top-1 -right-1 w-2 h-2 bg-[rgb(var(--color-primary))] rounded-full" }))] }), popoverOpen && ((0, jsx_runtime_1.jsxs)("div", { ref: popoverRef, className: (0, utils_1.cn)('absolute top-full right-0 mt-2 z-50', 'w-[95vw] sm:w-auto', 'bg-[rgb(var(--bg-surface))] rounded-xl shadow-xl border border-[rgb(var(--bd-default))]', 'overflow-hidden'), children: [(0, jsx_runtime_1.jsxs)("div", { className: "px-3 py-2 border-b border-[rgb(var(--bd-default))] flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-xs font-semibold text-[rgb(var(--fg-default))]", children: "Filters" }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setPopoverOpen(false), className: "w-5 h-5 rounded flex items-center justify-center hover:bg-[rgb(var(--bg-subtle))] transition-colors", children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { className: "w-3 h-3 text-[rgb(var(--fg-muted))]" }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "p-3 space-y-3", children: [filters.length > 0 && ((0, jsx_runtime_1.jsx)("div", { className: "grid gap-3", style: { gridTemplateColumns: `repeat(${Math.min(filters.length, 3)}, minmax(140px, 1fr))` }, children: filters.map((filter) => ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { className: "text-[10px] font-semibold text-[rgb(var(--fg-muted))] uppercase tracking-wider mb-1 block", children: filter.label }), (0, jsx_runtime_1.jsx)(dropdown_1.Dropdown, { options: filter.options, multiSelect: filter.type === 'multiselect', value: filterValues[filter.id] ?? (filter.type === 'multiselect' ? [] : ''), onValueChange: (value) => onFilterChange?.(filter.id, value), placeholder: filter.placeholder || `All ${filter.label}s`, searchable: true, className: "w-full" })] }, filter.id))) })), children, !hideDateRange && ((0, jsx_runtime_1.jsx)(date_picker_1.DatePicker, { mode: "range", value: dateRangeValue, onChange: handleDateRangeChange, inline: true }))] })] }))] })), forecast && onForecastChange && ((0, jsx_runtime_1.jsx)(ForecastControl, { forecast: forecast, onForecastChange: onForecastChange, timeFilter: timeFilter, onTimeFilterChange: onTimeFilterChange, bare: true })), (showInsights || reports.length > 0 || onShare || onExport) && ((0, jsx_runtime_1.jsx)(OverflowMenu, { showInsights: showInsights, insightsActive: insightsActive, onInsightsClick: onInsightsClick, reports: reports, onReportSelect: onReportSelect, onShare: onShare, onExport: onExport }))] }), showApplyButton && ((0, jsx_runtime_1.jsx)("button", { onClick: onApply, className: (0, utils_1.cn)('h-8 px-4 rounded-md flex items-center gap-1.5', 'bg-[rgb(var(--color-primary))] text-white', 'text-xs font-semibold', 'shadow-sm hover:shadow-md transition-all', 'hover:brightness-110'), children: "Apply" }))] })] }), contextLabel && ((0, jsx_runtime_1.jsxs)("span", { className: "sm:hidden mt-2 inline-flex items-center gap-1.5 h-6 px-2.5 rounded-full border border-[rgb(var(--bd-default))] bg-[rgb(var(--bg-subtle))] text-xs font-medium text-[rgb(var(--fg-muted))]", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.CalendarDays, { className: "w-3.5 h-3.5" }), contextLabel] })), (!hideTimeFilter || secondaryToggle) && ((0, jsx_runtime_1.jsxs)("div", { className: "sm:hidden mt-2 flex items-center gap-2 overflow-x-auto", children: [secondaryToggle && ((0, jsx_runtime_1.jsx)(tabs_1.Tabs, { tabs: secondaryToggle.options.map(opt => ({ id: opt.value, label: opt.label })), activeTab: secondaryToggle.value, onTabChange: secondaryToggle.onChange, size: "sm" })), !hideTimeFilter && ((0, jsx_runtime_1.jsx)(tabs_1.Tabs, { tabs: timeOptions.map(opt => ({ id: opt.value, label: opt.label, dropdownOptions: opt.dropdownOptions, quickOptions: opt.quickOptions })), activeTab: timeFilter, onTabChange: (tabId) => onTimeFilterChange?.(tabId), variant: "rounded", size: "sm", dropdownValues: {
439
+ : 'text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))]'), title: "Filters", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ListFilter, { className: "w-4 h-4" }), hasActiveFilters && !popoverOpen && ((0, jsx_runtime_1.jsx)("span", { className: "absolute -top-1 -right-1 w-2 h-2 bg-[rgb(var(--color-primary))] rounded-full" }))] }), popoverOpen && ((0, jsx_runtime_1.jsxs)("div", { ref: popoverRef, className: (0, utils_1.cn)('absolute top-full right-0 mt-2 z-50', 'w-[95vw] sm:w-auto', 'bg-[rgb(var(--bg-surface))] rounded-xl shadow-xl border border-[rgb(var(--bd-default))]', 'overflow-hidden'), children: [(0, jsx_runtime_1.jsxs)("div", { className: "px-3 py-2 border-b border-[rgb(var(--bd-default))] flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-xs font-semibold text-[rgb(var(--fg-default))]", children: "Filters" }), (0, jsx_runtime_1.jsx)("button", { onClick: () => setPopoverOpen(false), className: "w-5 h-5 rounded flex items-center justify-center hover:bg-[rgb(var(--bg-subtle))] transition-colors", children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { className: "w-3 h-3 text-[rgb(var(--fg-muted))]" }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "p-3 space-y-3", children: [filters.length > 0 && ((0, jsx_runtime_1.jsx)("div", { className: "grid gap-3", style: { gridTemplateColumns: `repeat(${Math.min(filters.length, 3)}, minmax(140px, 1fr))` }, children: filters.map((filter) => ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { className: "text-[10px] font-semibold text-[rgb(var(--fg-muted))] uppercase tracking-wider mb-1 block", children: filter.label }), (0, jsx_runtime_1.jsx)(dropdown_1.Dropdown, { options: filter.options, multiSelect: filter.type === 'multiselect', value: filterValues[filter.id] ?? (filter.type === 'multiselect' ? [] : ''), onValueChange: (value) => onFilterChange?.(filter.id, value), placeholder: filter.placeholder || `All ${filter.label}s`, searchable: true, className: "w-full" })] }, filter.id))) })), children, !hideDateRange && ((0, jsx_runtime_1.jsx)(date_picker_1.DatePicker, { mode: "range", value: dateRangeValue, onChange: handleDateRangeChange, inline: true }))] })] }))] })), forecast && onForecastChange && ((0, jsx_runtime_1.jsx)(ForecastControl, { forecast: forecast, onForecastChange: onForecastChange, timeFilter: timeFilter, onTimeFilterChange: onTimeFilterChange, bare: true })), (showInsights || reports.length > 0 || onShare || onExport) && ((0, jsx_runtime_1.jsx)(OverflowMenu, { showInsights: showInsights, insightsActive: insightsActive, onInsightsClick: onInsightsClick, reports: reports, onReportSelect: onReportSelect, onShare: onShare, onExport: onExport }))] }), showApplyButton && ((0, jsx_runtime_1.jsx)("button", { onClick: onApply, className: (0, utils_1.cn)('h-8 px-4 rounded-md flex items-center gap-1.5', 'bg-[rgb(var(--color-primary))] text-white', 'text-xs font-semibold', 'shadow-sm hover:shadow-md transition-all', 'hover:brightness-110'), children: "Apply" }))] })] }), contextLabel && ((0, jsx_runtime_1.jsxs)("span", { className: "sm:hidden mt-2 inline-flex items-center gap-1.5 h-6 px-2.5 rounded-full border border-[rgb(var(--bd-default))] bg-[rgb(var(--bg-subtle))] text-xs font-medium text-[rgb(var(--fg-muted))]", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.CalendarDays, { className: "w-3.5 h-3.5" }), contextLabel] })), (!hideTimeFilter || secondaryToggle) && ((0, jsx_runtime_1.jsxs)("div", { className: "sm:hidden mt-2 flex flex-col items-start gap-2", children: [secondaryToggle && ((0, jsx_runtime_1.jsx)(tabs_1.Tabs, { tabs: secondaryToggle.options.map(opt => ({ id: opt.value, label: opt.label })), activeTab: secondaryToggle.value, onTabChange: secondaryToggle.onChange, size: "sm" })), !hideTimeFilter && ((0, jsx_runtime_1.jsx)(tabs_1.Tabs, { tabs: timeOptions.map(opt => ({ id: opt.value, label: opt.label, dropdownOptions: opt.dropdownOptions, quickOptions: opt.quickOptions })), activeTab: timeFilter, onTabChange: (tabId) => onTimeFilterChange?.(tabId), variant: "rounded", size: "sm", dropdownValues: {
417
440
  Day: timeFilterSelection?.days || [],
418
441
  Week: timeFilterSelection?.weeks || [],
419
442
  Month: timeFilterSelection?.months || [],
@@ -430,5 +453,5 @@ function FilterBar({ title, contextLabel, secondaryToggle, timeFilter = 'Week',
430
453
  ...(filter === 'Year' && { years: selectedValues }),
431
454
  };
432
455
  onTimeFilterSelectionChange?.(newSelection);
433
- } }))] }))] }));
456
+ } }))] })), description && (0, jsx_runtime_1.jsx)("p", { className: "mt-1 text-sm text-[rgb(var(--fg-muted))]", children: description })] }));
434
457
  }
@@ -43,44 +43,77 @@ const fiscalWeekCount = (fyStart) => {
43
43
  return wk;
44
44
  };
45
45
  exports.fiscalWeekCount = fiscalWeekCount;
46
- // "this" anchors to the FY's first period (W1 / Apr / Q1), per fiscal reporting.
47
- const rangeFromSelection = (sel, fyStart) => {
46
+ const startOfDay = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate());
47
+ const addDays = (d, n) => new Date(d.getFullYear(), d.getMonth(), d.getDate() + n);
48
+ // Monday of the week holding `d` (weeks run Monday to Sunday, as the fiscal weeks do).
49
+ const mondayOf = (d) => addDays(d, -((d.getDay() + 6) % 7));
50
+ /**
51
+ * The dates a selection covers.
52
+ *
53
+ * Quick picks (This / Last …) count from `today`: "This week" is the week holding today, "Last
54
+ * month" the calendar month before this one, "This quarter" the fiscal quarter holding today.
55
+ * They used to anchor to the financial year's first period, so "This week" meant Week 1 in April.
56
+ * A "This …" range runs to today, since nothing after today has happened yet.
57
+ *
58
+ * A numbered pick (W27, month "9", Q3, a year "2025") is that period of the financial year
59
+ * starting `fyStart`. Year "all" is all time, from the earliest date a book could hold to today.
60
+ */
61
+ const rangeFromSelection = (sel, fyStart, today = new Date()) => {
62
+ const t = startOfDay(today);
48
63
  const fyOpen = new Date(fyStart, 3, 1);
49
64
  const fyClose = new Date(fyStart + 1, 2, 31);
50
65
  const yearOf = (mo) => (mo >= 3 ? fyStart : fyStart + 1);
51
66
  const monthRange = (mo) => ({ from: new Date(yearOf(mo), mo, 1), to: new Date(yearOf(mo), mo + 1, 0) });
52
67
  const type = sel?.type || 'Month';
53
68
  if (type === 'Day') {
54
- const now = new Date();
55
- const y = now.getFullYear(), m = now.getMonth(), d = now.getDate();
56
69
  const pick = sel?.days?.[0] || 'today';
57
- if (pick === 'yesterday') {
58
- const dt = new Date(y, m, d - 1);
59
- return { from: dt, to: dt };
60
- }
61
- if (pick === 'last7')
62
- return { from: new Date(y, m, d - 6), to: new Date(y, m, d) };
63
- if (pick === 'last14')
64
- return { from: new Date(y, m, d - 13), to: new Date(y, m, d) };
65
- if (pick === 'last30')
66
- return { from: new Date(y, m, d - 29), to: new Date(y, m, d) };
67
- return { from: new Date(y, m, d), to: new Date(y, m, d) };
70
+ if (pick === 'yesterday')
71
+ return { from: addDays(t, -1), to: addDays(t, -1) };
72
+ const span = { last7: 7, last14: 14, last30: 30 }[pick];
73
+ return span ? { from: addDays(t, -(span - 1)), to: t } : { from: t, to: t };
68
74
  }
69
75
  if (type === 'Week') {
70
- const pick = sel?.weeks?.[0] || 'W1';
76
+ const pick = sel?.weeks?.[0] || 'this';
77
+ const monday = mondayOf(t);
78
+ if (pick === 'this')
79
+ return { from: monday, to: t };
80
+ if (pick === 'last')
81
+ return { from: addDays(monday, -7), to: addDays(monday, -1) };
82
+ // "Last 2 weeks" is this week and the one before it, to today.
83
+ const weeks = { last2: 2, last4: 4 }[pick];
84
+ if (weeks)
85
+ return { from: addDays(monday, -7 * (weeks - 1)), to: t };
71
86
  const wk = /^W\d+$/i.test(pick) ? parseInt(pick.slice(1)) : 1;
72
87
  return (0, exports.fiscalWeekRange)(fyStart, wk) || { from: fyOpen, to: fyClose };
73
88
  }
74
89
  if (type === 'Quarter') {
75
- const pick = sel?.quarters?.[0];
76
- const qNum = pick && /^q[1-4]$/i.test(pick) ? parseInt(pick.slice(1)) - 1 : 0;
90
+ const pick = sel?.quarters?.[0] || 'this';
91
+ // The fiscal quarter holding today: Apr–Jun is Q1.
92
+ const qStart = new Date(t.getFullYear(), t.getMonth() - (((t.getMonth() - 3 + 12) % 12) % 3), 1);
93
+ if (pick === 'this')
94
+ return { from: qStart, to: t };
95
+ if (pick === 'last')
96
+ return { from: new Date(qStart.getFullYear(), qStart.getMonth() - 3, 1), to: addDays(qStart, -1) };
97
+ const qNum = /^q[1-4]$/i.test(pick) ? parseInt(pick.slice(1)) - 1 : 0;
77
98
  const startMonth = (3 + qNum * 3) % 12;
78
99
  const sy = yearOf(startMonth);
79
100
  return { from: new Date(sy, startMonth, 1), to: new Date(sy, startMonth + 3, 0) };
80
101
  }
81
- if (type === 'Year')
82
- return { from: fyOpen, to: fyClose };
83
- const picks = (sel?.months || []).filter(s => /^\d{1,2}$/.test(s)).map(s => parseInt(s));
102
+ if (type === 'Year') {
103
+ const pick = sel?.years?.[0] || 'this';
104
+ if (pick === 'all')
105
+ return { from: new Date(1970, 0, 1), to: t };
106
+ if (pick === 'last')
107
+ return { from: new Date(fyStart - 1, 3, 1), to: new Date(fyStart, 2, 31) };
108
+ const fy = /^\d{4}$/.test(pick) ? parseInt(pick) : fyStart;
109
+ return { from: new Date(fy, 3, 1), to: new Date(fy + 1, 2, 31) };
110
+ }
111
+ const months = sel?.months || [];
112
+ if (months.includes('this'))
113
+ return { from: new Date(t.getFullYear(), t.getMonth(), 1), to: t };
114
+ if (months.includes('last'))
115
+ return { from: new Date(t.getFullYear(), t.getMonth() - 1, 1), to: new Date(t.getFullYear(), t.getMonth(), 0) };
116
+ const picks = months.filter(s => /^\d{1,2}$/.test(s)).map(s => parseInt(s));
84
117
  if (picks.length > 0) {
85
118
  const ranges = picks.map(monthRange);
86
119
  return {
@@ -88,7 +121,7 @@ const rangeFromSelection = (sel, fyStart) => {
88
121
  to: ranges.reduce((a, b) => (b.to > a.to ? b : a)).to,
89
122
  };
90
123
  }
91
- return monthRange(3);
124
+ return { from: new Date(t.getFullYear(), t.getMonth(), 1), to: t };
92
125
  };
93
126
  exports.rangeFromSelection = rangeFromSelection;
94
127
  // Header subtitle for the selected period. Week/Day show their day span in brackets;
@@ -106,17 +139,28 @@ const buildContextLabel = (sel, fyStart, dateFrom, dateTo, t) => {
106
139
  return `${pick} (${dayRange})`;
107
140
  }
108
141
  if (!dateFrom && !dateTo && sel?.type === 'Week') {
109
- const w = sel.weeks?.[0];
110
- const wk = w && /^W\d+$/i.test(w) ? w.slice(1) : '1';
142
+ const w = sel.weeks?.[0] || 'this';
143
+ const quick = { this: 'This Week', last: 'Last Week', last2: 'Last 2 Weeks', last4: 'Last 4 Weeks' }[w];
144
+ if (quick)
145
+ return `${t(quick)} (${dayRange})`;
146
+ const wk = /^W\d+$/i.test(w) ? w.slice(1) : '1';
111
147
  return `${t('Week')} ${wk} (${dayRange})`;
112
148
  }
113
149
  if (!dateFrom && !dateTo && sel?.type === 'Quarter') {
114
- const q = sel.quarters?.[0];
115
- const label = q && /^q[1-4]$/i.test(q) ? `Q${q.slice(1)}` : 'Q1';
150
+ const q = sel.quarters?.[0] || 'this';
151
+ const label = q === 'this' ? t('This Quarter') : q === 'last' ? t('Last Quarter') : /^q[1-4]$/i.test(q) ? `Q${q.slice(1)}` : 'Q1';
116
152
  return `${label} (${fmtMon(from)} – ${fmtMon(to)})`;
117
153
  }
118
154
  if (!dateFrom && !dateTo && sel?.type === 'Year') {
119
- return `${t('FY')} ${fyStart}-${fyStart + 1}`;
155
+ const y = sel.years?.[0] || 'this';
156
+ if (y === 'all')
157
+ return t('All Time');
158
+ const fy = y === 'last' ? fyStart - 1 : /^\d{4}$/.test(y) ? parseInt(y) : fyStart;
159
+ return `${t('FY')} ${fy}-${fy + 1}`;
160
+ }
161
+ const m0 = sel?.months?.[0];
162
+ if (!dateFrom && !dateTo && sel?.type === 'Month' && (m0 === 'this' || m0 === 'last')) {
163
+ return `${t(m0 === 'this' ? 'This Month' : 'Last Month')} (${dayRange})`;
120
164
  }
121
165
  const sameMonth = from.getFullYear() === to.getFullYear() && from.getMonth() === to.getMonth();
122
166
  if (sameMonth)
@@ -52,6 +52,7 @@ const utils_2 = require("../../lib/utils.js");
52
52
  const react_dom_1 = require("react-dom");
53
53
  const SidebarContextMenu_1 = require("./SidebarContextMenu.js");
54
54
  const SidebarPreferencesContext_1 = require("../../contexts/SidebarPreferencesContext.js");
55
+ const SessionAdapterContext_1 = require("../../contexts/SessionAdapterContext.js");
55
56
  // Route mapping imported from shared utility: getModuleRoutePath
56
57
  // Routes that support quick-create via sidebar "+" button.
57
58
  // When clicked, navigates to {route}?create=true — the page opens its create modal.
@@ -162,6 +163,14 @@ function DynamicSidebar({ isCollapsed = false, onMenuItemClick, onToggleSidebar,
162
163
  const [expandedSubModules, setExpandedSubModules] = React.useState([]);
163
164
  const [loading, setLoading] = React.useState(true);
164
165
  const [error, setError] = React.useState(null);
166
+ // The menu API needs the signed-in session (company credentials). Read from the session adapter,
167
+ // not next-auth, so the library does not require next-auth; held in a ref so a fresh session
168
+ // object does not refetch the menu, only the session arriving or leaving does.
169
+ const sessionAdapter = (0, SessionAdapterContext_1.useOptionalSessionAdapter)();
170
+ const sessionRef = React.useRef(sessionAdapter?.data ?? null);
171
+ sessionRef.current = sessionAdapter?.data ?? null;
172
+ const hasSession = Boolean(sessionAdapter?.data);
173
+ const sessionStatus = sessionAdapter?.status ?? 'unauthenticated';
165
174
  const [needsScroll, setNeedsScroll] = React.useState(false);
166
175
  const navRef = React.useRef(null);
167
176
  const [hoveredGroup, setHoveredGroup] = React.useState(null);
@@ -203,6 +212,12 @@ function DynamicSidebar({ isCollapsed = false, onMenuItemClick, onToggleSidebar,
203
212
  if (!companyId || !userId) {
204
213
  return;
205
214
  }
215
+ // No session yet: wait for it (this runs again when it arrives); none at all: no menu.
216
+ if (!hasSession) {
217
+ if (sessionStatus !== 'loading')
218
+ setLoading(false);
219
+ return;
220
+ }
206
221
  let isCancelled = false;
207
222
  const fetchNavigation = async () => {
208
223
  // Check cache first - include session start time to invalidate cache across logins
@@ -228,7 +243,7 @@ function DynamicSidebar({ isCollapsed = false, onMenuItemClick, onToggleSidebar,
228
243
  try {
229
244
  setLoading(true);
230
245
  setError(null);
231
- const navigation = await (0, api_1.getDynamicNavigation)(companyId, userId);
246
+ const navigation = await (0, api_1.getDynamicNavigation)(companyId, userId, sessionRef.current);
232
247
  if (!isCancelled) {
233
248
  setGroupedModules(navigation);
234
249
  // Cache the result
@@ -256,7 +271,7 @@ function DynamicSidebar({ isCollapsed = false, onMenuItemClick, onToggleSidebar,
256
271
  clearTimeout(hoverTimeoutRef.current);
257
272
  }
258
273
  };
259
- }, [companyId, userId]);
274
+ }, [companyId, userId, hasSession, sessionStatus]);
260
275
  // Auto-open parent items based on current path (optimized)
261
276
  React.useEffect(() => {
262
277
  if (groupedModules.length === 0)
@@ -8,14 +8,14 @@ const navigation_1 = require("next/navigation");
8
8
  const lucide_react_1 = require("lucide-react");
9
9
  const utils_1 = require("../../lib/utils.js");
10
10
  const LanguageContext_1 = require("../../contexts/LanguageContext.js");
11
- const react_2 = require("next-auth/react");
11
+ const SessionAdapterContext_1 = require("../../contexts/SessionAdapterContext.js");
12
12
  const MessagingContext_1 = require("../../contexts/MessagingContext.js");
13
13
  const messaging_1 = require("../../lib/api/activity/messaging/messaging.js");
14
14
  const conversation_list_1 = require("./conversation-list.js");
15
15
  const ui_1 = require("../ui/index.js");
16
16
  function MessagingPanelContent({ onClose }) {
17
17
  const { t } = (0, LanguageContext_1.useLanguage)();
18
- const { data: session } = (0, react_2.useSession)();
18
+ const session = (0, SessionAdapterContext_1.useOptionalSessionAdapter)()?.data ?? null;
19
19
  const { state, actions } = (0, MessagingContext_1.useMessaging)();
20
20
  const router = (0, navigation_1.useRouter)();
21
21
  const currentUserId = String(session?.user?.UserID || session?.user?.userID || '');
@@ -80,7 +80,10 @@ function Tabs({ tabs, activeTab, onTabChange, fullWidth = false, className = '',
80
80
  ? 'bg-[rgb(var(--color-primary))] text-white border-[rgb(var(--color-primary))]'
81
81
  : 'bg-[rgb(var(--bg-surface))] text-[rgb(var(--fg-muted))] border-[rgb(var(--bd-default))] hover:border-[rgb(var(--color-primary))] hover:text-[rgb(var(--color-primary))]'), children: q.label }, q.value));
82
82
  }) })) : undefined;
83
- return ((0, jsx_runtime_1.jsx)(dropdown_1.Dropdown, { options: tab.dropdownOptions, value: selected, onValueChange: (sel) => {
83
+ return ((0, jsx_runtime_1.jsx)(dropdown_1.Dropdown, { options: tab.dropdownOptions,
84
+ // Periods are listed in time order (Apr, May, Jun… / Week 1, 2, 3…); sorted by label,
85
+ // the months came out Apr, Aug, Dec, Feb.
86
+ preserveOrder: true, value: selected, onValueChange: (sel) => {
84
87
  const arr = Array.isArray(sel) ? sel.map(String) : [String(sel)];
85
88
  // Ticking a named option clears any quick preset (This/Last) — they're exclusive.
86
89
  const quickVals = (tab.quickOptions || []).map(q => q.value);
@@ -8,7 +8,7 @@ exports.useEmailActions = useEmailActions;
8
8
  exports.useEmailAPI = useEmailAPI;
9
9
  const jsx_runtime_1 = require("react/jsx-runtime");
10
10
  const react_1 = require("react");
11
- const react_2 = require("next-auth/react");
11
+ const SessionAdapterContext_1 = require("./SessionAdapterContext.js");
12
12
  const email_1 = require("../lib/api/activity/email/index.js");
13
13
  const FOLDER_CACHE_MAX_AGE = 5 * 60 * 1000; // 5 minutes
14
14
  // Initial State
@@ -152,11 +152,12 @@ function emailReducer(state, action) {
152
152
  const EmailContext = (0, react_1.createContext)(undefined);
153
153
  // Email Provider Component
154
154
  function EmailProvider({ children }) {
155
- const { data: session } = (0, react_2.useSession)();
155
+ const session = (0, SessionAdapterContext_1.useOptionalSessionAdapter)()?.data ?? null;
156
156
  const [state, dispatch] = (0, react_1.useReducer)(emailReducer, initialState);
157
157
  // Memoize on stable identity — useSession() returns a new object each render, so an
158
158
  // unmemoized api would churn every useCallback([api]) and trigger repeated fetches.
159
159
  const sessionIdentity = `${session?.user?.CompanyID ?? ''}:${session?.user?.UserID ?? ''}`;
160
+ // The adapter carries the next-auth session as AuthSessionProvider received it, so it has the shape the email API reads.
160
161
  const api = (0, react_1.useMemo)(() => (0, email_1.createEmailAPI)(session), [sessionIdentity]); // eslint-disable-line react-hooks/exhaustive-deps
161
162
  // Initialize email system when session is available
162
163
  // NOTE: Initialization is now manual - call actions.fetchEmails() when needed
@@ -6,7 +6,7 @@ exports.useMessaging = useMessaging;
6
6
  const jsx_runtime_1 = require("react/jsx-runtime");
7
7
  const react_1 = require("react");
8
8
  const signalr_1 = require("@microsoft/signalr");
9
- const react_2 = require("next-auth/react");
9
+ const SessionAdapterContext_1 = require("./SessionAdapterContext.js");
10
10
  const messaging_1 = require("../lib/api/activity/messaging/messaging.js");
11
11
  const messaging_2 = require("../lib/api/activity/messaging/messaging.js");
12
12
  const config_1 = require("../lib/api/core/config.js");
@@ -196,7 +196,7 @@ function messagingReducer(state, action) {
196
196
  const MessagingContext = (0, react_1.createContext)(undefined);
197
197
  // ─── Provider ───────────────────────────────────────────────────────────────
198
198
  function MessagingProvider({ children }) {
199
- const { data: session } = (0, react_2.useSession)();
199
+ const session = (0, SessionAdapterContext_1.useOptionalSessionAdapter)()?.data ?? null;
200
200
  const [state, dispatch] = (0, react_1.useReducer)(messagingReducer, initialState);
201
201
  const stateRef = (0, react_1.useRef)(state);
202
202
  stateRef.current = state;
@@ -42,6 +42,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
42
42
  const React = __importStar(require("react"));
43
43
  const api_1 = require("../lib/api/index.js");
44
44
  const utils_1 = require("../lib/utils.js");
45
+ const SessionAdapterContext_1 = require("./SessionAdapterContext.js");
45
46
  /**
46
47
  * Filter grouped modules to only those with MobileSupported = 1/true.
47
48
  * Returns grouped structure with empty groups removed.
@@ -59,11 +60,25 @@ function NavigationProvider({ children, companyId, userId }) {
59
60
  const [groupedModules, setGroupedModules] = React.useState([]);
60
61
  const [loading, setLoading] = React.useState(true);
61
62
  const [error, setError] = React.useState(null);
63
+ // The menu API needs the signed-in session (company credentials). Read from the session adapter,
64
+ // not next-auth, so the library does not require next-auth; held in a ref so a fresh session
65
+ // object does not refetch the menu, only the session arriving or leaving does.
66
+ const sessionAdapter = (0, SessionAdapterContext_1.useOptionalSessionAdapter)();
67
+ const sessionRef = React.useRef(sessionAdapter?.data ?? null);
68
+ sessionRef.current = sessionAdapter?.data ?? null;
69
+ const hasSession = Boolean(sessionAdapter?.data);
70
+ const sessionStatus = sessionAdapter?.status ?? 'unauthenticated';
62
71
  const fetchNavigation = React.useCallback(async () => {
63
72
  if (!companyId || !userId) {
64
73
  setLoading(false);
65
74
  return;
66
75
  }
76
+ // No session yet: wait for it (this runs again when it arrives); none at all: no menu.
77
+ if (!hasSession) {
78
+ if (sessionStatus !== 'loading')
79
+ setLoading(false);
80
+ return;
81
+ }
67
82
  const cacheKey = `navigation_${companyId}_${userId}`;
68
83
  const cacheTimeKey = `${cacheKey}_time`;
69
84
  const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
@@ -80,7 +95,7 @@ function NavigationProvider({ children, companyId, userId }) {
80
95
  }
81
96
  }
82
97
  setLoading(true);
83
- const navigation = await (0, api_1.getDynamicNavigation)(companyId, userId);
98
+ const navigation = await (0, api_1.getDynamicNavigation)(companyId, userId, sessionRef.current);
84
99
  setGroupedModules(navigation);
85
100
  localStorage.setItem(cacheKey, JSON.stringify(navigation));
86
101
  localStorage.setItem(cacheTimeKey, Date.now().toString());
@@ -92,7 +107,7 @@ function NavigationProvider({ children, companyId, userId }) {
92
107
  finally {
93
108
  setLoading(false);
94
109
  }
95
- }, [companyId, userId]);
110
+ }, [companyId, userId, hasSession, sessionStatus]);
96
111
  React.useEffect(() => {
97
112
  fetchNavigation();
98
113
  }, [fetchNavigation]);
@@ -5,7 +5,7 @@ exports.NotificationProvider = NotificationProvider;
5
5
  exports.useNotifications = useNotifications;
6
6
  const jsx_runtime_1 = require("react/jsx-runtime");
7
7
  const react_1 = require("react");
8
- const react_2 = require("next-auth/react");
8
+ const SessionAdapterContext_1 = require("./SessionAdapterContext.js");
9
9
  const activity_1 = require("../lib/api/activity/index.js");
10
10
  const types_1 = require("../lib/api/activity/notifications/types.js");
11
11
  const useSignalR_1 = require("../hooks/useSignalR.js");
@@ -15,7 +15,7 @@ const NotificationContext = (0, react_1.createContext)(undefined);
15
15
  * app. Consumers read shared state via `useNotifications()` instead of each mounting their own.
16
16
  */
17
17
  function NotificationProvider({ children }) {
18
- const { data: session } = (0, react_2.useSession)();
18
+ const session = (0, SessionAdapterContext_1.useOptionalSessionAdapter)()?.data ?? null;
19
19
  const [notifications, setNotifications] = (0, react_1.useState)([]);
20
20
  const [notificationsLoading, setNotificationsLoading] = (0, react_1.useState)(false);
21
21
  const [showNotificationDropdown, setShowNotificationDropdown] = (0, react_1.useState)(false);
@@ -2,6 +2,7 @@
2
2
  "use strict";
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.SessionAdapterProvider = SessionAdapterProvider;
5
+ exports.useOptionalSessionAdapter = useOptionalSessionAdapter;
5
6
  exports.useSessionAdapter = useSessionAdapter;
6
7
  const jsx_runtime_1 = require("react/jsx-runtime");
7
8
  const react_1 = require("react");
@@ -14,6 +15,13 @@ function SessionAdapterProvider({ user, status, signOut, children }) {
14
15
  }), [user, status, signOut]);
15
16
  return (0, jsx_runtime_1.jsx)(SessionAdapterContext.Provider, { value: value, children: children });
16
17
  }
18
+ /**
19
+ * The session when a provider is present, null when not: for components that also render in apps
20
+ * without one, such as the dynamic sidebar, which simply loads no menu then.
21
+ */
22
+ function useOptionalSessionAdapter() {
23
+ return (0, react_1.useContext)(SessionAdapterContext);
24
+ }
17
25
  /** Drop-in replacement for next-auth's `useSession()` inside library components. */
18
26
  function useSessionAdapter() {
19
27
  const ctx = (0, react_1.useContext)(SessionAdapterContext);
@@ -23,13 +23,13 @@
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
24
  exports.useApproval = useApproval;
25
25
  const react_1 = require("react");
26
- const react_2 = require("next-auth/react");
26
+ const SessionAdapterContext_1 = require("../contexts/SessionAdapterContext.js");
27
27
  const approval_1 = require("../lib/api/approval/index.js");
28
28
  /**
29
29
  * useApproval Hook
30
30
  */
31
31
  function useApproval() {
32
- const { data: session } = (0, react_2.useSession)();
32
+ const session = (0, SessionAdapterContext_1.useOptionalSessionAdapter)()?.data ?? null;
33
33
  // State
34
34
  const [pendingApprovals, setPendingApprovals] = (0, react_1.useState)([]);
35
35
  const [isLoading, setIsLoading] = (0, react_1.useState)(false);
@@ -17,10 +17,10 @@
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.useBusinessAudit = useBusinessAudit;
19
19
  const react_1 = require("react");
20
- const react_2 = require("next-auth/react");
20
+ const SessionAdapterContext_1 = require("../contexts/SessionAdapterContext.js");
21
21
  const audit_1 = require("../lib/api/activity/audit/index.js");
22
22
  function useBusinessAudit() {
23
- const { data: session } = (0, react_2.useSession)();
23
+ const session = (0, SessionAdapterContext_1.useOptionalSessionAdapter)()?.data ?? null;
24
24
  const [isLogging, setIsLogging] = (0, react_1.useState)(false);
25
25
  const logAction = (0, react_1.useCallback)(async (action, module, details) => {
26
26
  if (!session)
@@ -22,13 +22,13 @@
22
22
  Object.defineProperty(exports, "__esModule", { value: true });
23
23
  exports.useDraft = useDraft;
24
24
  const react_1 = require("react");
25
- const react_2 = require("next-auth/react");
25
+ const SessionAdapterContext_1 = require("../contexts/SessionAdapterContext.js");
26
26
  const draft_1 = require("../lib/api/draft/index.js");
27
27
  /**
28
28
  * useDraft Hook - Milestone Based
29
29
  */
30
30
  function useDraft(module, currentState) {
31
- const { data: session } = (0, react_2.useSession)();
31
+ const session = (0, SessionAdapterContext_1.useOptionalSessionAdapter)()?.data ?? null;
32
32
  // State
33
33
  const [drafts, setDrafts] = (0, react_1.useState)([]);
34
34
  const [isLoadingDrafts, setIsLoadingDrafts] = (0, react_1.useState)(false);