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
@@ -10,15 +10,16 @@ exports.fetchDynamicMenuModules = fetchDynamicMenuModules;
10
10
  exports.groupAndSortModules = groupAndSortModules;
11
11
  exports.getDynamicNavigation = getDynamicNavigation;
12
12
  const client_1 = __importDefault(require("../core/client.js"));
13
- const react_1 = require("next-auth/react");
14
13
  /**
15
14
  * Fetch dynamic menu modules from external API
16
- * Now uses APIClient for proper authentication with company credentials
15
+ * Now uses APIClient for proper authentication with company credentials.
16
+ *
17
+ * The caller passes the session (from useSessionAdapter, which AuthSessionProvider fills from
18
+ * next-auth) rather than this module calling next-auth's getSession: importing next-auth here made
19
+ * every app installing indas-ui install next-auth too, since nearly every entry reaches this file.
17
20
  */
18
- async function fetchDynamicMenuModules(companyId, userId) {
21
+ async function fetchDynamicMenuModules(companyId, userId, session) {
19
22
  try {
20
- // Get session to pass to APIClient for company credentials
21
- const session = await (0, react_1.getSession)();
22
23
  if (!session) {
23
24
  return [];
24
25
  }
@@ -127,10 +128,10 @@ function groupAndSortModules(modules) {
127
128
  /**
128
129
  * Get dynamic navigation structure for sidebar
129
130
  */
130
- async function getDynamicNavigation(companyId, userId) {
131
+ async function getDynamicNavigation(companyId, userId, session) {
131
132
  if (!companyId || !userId) {
132
133
  return [];
133
134
  }
134
- const modules = await fetchDynamicMenuModules(companyId, userId);
135
+ const modules = await fetchDynamicMenuModules(companyId, userId, session);
135
136
  return groupAndSortModules(modules);
136
137
  }
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  "use strict";
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.cn = exports.QueryProvider = exports.useAPI = exports.APIProvider = exports.AuthSessionProvider = exports.useSessionAdapter = exports.SessionAdapterProvider = exports.useCurrency = exports.CurrencyProvider = exports.usePageTitle = exports.PageTitleProvider = exports.useSearchPreferences = exports.SearchPreferencesProvider = exports.useDevice = exports.DeviceProvider = exports.useLanguage = exports.LanguageProvider = exports.useAppConfig = exports.AppConfigProvider = exports.useQuickAlert = exports.useGlobalAlert = exports.GlobalAlertProvider = exports.ThemeContext = exports.ThemeScript = exports.ThemeProvider = void 0;
4
+ exports.cn = exports.QueryProvider = exports.useAPI = exports.APIProvider = exports.useSessionAdapter = exports.SessionAdapterProvider = exports.useCurrency = exports.CurrencyProvider = exports.usePageTitle = exports.PageTitleProvider = exports.useSearchPreferences = exports.SearchPreferencesProvider = exports.useDevice = exports.DeviceProvider = exports.useLanguage = exports.LanguageProvider = exports.useAppConfig = exports.AppConfigProvider = exports.useQuickAlert = exports.useGlobalAlert = exports.GlobalAlertProvider = exports.ThemeContext = exports.ThemeScript = exports.ThemeProvider = void 0;
5
5
  // Providers entry: `import { ThemeProvider } from 'indas-ui/providers'`
6
6
  var ThemeContext_1 = require("./contexts/ThemeContext.js");
7
7
  Object.defineProperty(exports, "ThemeProvider", { enumerable: true, get: function () { return ThemeContext_1.ThemeProvider; } });
@@ -32,8 +32,8 @@ Object.defineProperty(exports, "useCurrency", { enumerable: true, get: function
32
32
  var SessionAdapterContext_1 = require("./contexts/SessionAdapterContext.js");
33
33
  Object.defineProperty(exports, "SessionAdapterProvider", { enumerable: true, get: function () { return SessionAdapterContext_1.SessionAdapterProvider; } });
34
34
  Object.defineProperty(exports, "useSessionAdapter", { enumerable: true, get: function () { return SessionAdapterContext_1.useSessionAdapter; } });
35
- var session_provider_1 = require("./components/providers/session-provider.js");
36
- Object.defineProperty(exports, "AuthSessionProvider", { enumerable: true, get: function () { return session_provider_1.AuthSessionProvider; } });
35
+ // AuthSessionProvider is in 'indas-ui/auth': it is the one part built on next-auth, which apps
36
+ // install only when they use it.
37
37
  var api_provider_1 = require("./components/providers/api-provider.js");
38
38
  Object.defineProperty(exports, "APIProvider", { enumerable: true, get: function () { return api_provider_1.APIProvider; } });
39
39
  Object.defineProperty(exports, "useAPI", { enumerable: true, get: function () { return api_provider_1.useAPI; } });
@@ -1,3 +1,2 @@
1
- 'use client';
2
1
  export { ProtectedRoute } from './ProtectedRoute.js';
3
2
  export { UnauthorizedPage, MobileUnavailablePage } from './UnauthorizedPage.js';
@@ -5,7 +5,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
5
5
  * Domain-specific branding (logo, title, messages) is injected via props.
6
6
  */
7
7
  import { useState, useEffect, useCallback, useRef } from 'react';
8
- import { useSession } from 'next-auth/react';
8
+ import { useOptionalSessionAdapter } from '../../contexts/SessionAdapterContext.js';
9
9
  import { useRouter } from 'next/navigation';
10
10
  import { X, Plus, Maximize2, Download, Share2, Volume2, VolumeX, Bot, PanelLeftOpen, Star, Pencil, Trash2, ChevronDown, Search, Calendar } from 'lucide-react';
11
11
  import { cn } from '../../lib/utils.js';
@@ -62,7 +62,7 @@ function bucketForDate(iso, now = new Date()) {
62
62
  return { id: `m-${d.getFullYear()}-${d.getMonth()}`, label: monthLabel, sort };
63
63
  }
64
64
  export function Chat({ mode = 'fullpage', externalMuted, title = 'Chat', sidebarTitle, bubbleLogo, logo, MessagesComponent, fullpagePath, onBeforeEditMessage, initialMessage, initialConversationId, headerActions, onMessagesChange }) {
65
- const { data: session } = useSession();
65
+ const session = useOptionalSessionAdapter()?.data ?? null;
66
66
  const router = useRouter();
67
67
  const alerts = useGlobalAlert();
68
68
  const { t } = useLanguage();
@@ -1,4 +1,3 @@
1
- 'use client';
2
1
  // Chat Components - Centralized exports
3
2
  //
4
3
  // Only the backend-agnostic pieces are exported. `Chat` itself is deliberately left out: it is
@@ -50,6 +50,8 @@ export interface FilterBarProps {
50
50
  title: string;
51
51
  /** Dynamic context label (shown as subtitle) */
52
52
  contextLabel?: string;
53
+ /** One line under the title saying what the page shows. */
54
+ description?: React.ReactNode;
53
55
  /** Secondary toggle (e.g., Job/Value) */
54
56
  secondaryToggle?: {
55
57
  options: ToggleOption[];
@@ -99,10 +101,12 @@ export interface FilterBarProps {
99
101
  onInsightsClick?: () => void;
100
102
  /** Fiscal-year start year (e.g. 2026 for FY 2026-2027). Drives fiscal Week/Month/Quarter/Year options. */
101
103
  fiscalStartYear?: number;
104
+ /** Offer "All Time" at the end of the Year list (needs `fiscalStartYear`). */
105
+ allTime?: boolean;
102
106
  /** Additional content */
103
107
  children?: React.ReactNode;
104
108
  className?: string;
105
109
  /** Layout variant: 'default' or 'centered' (title in center) */
106
110
  layout?: 'default' | 'centered';
107
111
  }
108
- export declare function FilterBar({ title, contextLabel, secondaryToggle, timeFilter, onTimeFilterChange, timeFilterSelection, onTimeFilterSelectionChange, hideTimeFilter, filters, filterValues, onFilterChange, dateFrom, dateTo, onDateFromChange, onDateToChange, onApply, onReset, reports, onReportSelect, onExport, hideFilterButton, hideDateRange, showApplyButton, forecast, onForecastChange, onShare, showInsights, insightsActive, onInsightsClick, children, className, layout, fiscalStartYear }: FilterBarProps): React.JSX.Element;
112
+ export declare function FilterBar({ title, contextLabel, description, secondaryToggle, timeFilter, onTimeFilterChange, timeFilterSelection, onTimeFilterSelectionChange, hideTimeFilter, filters, filterValues, onFilterChange, dateFrom, dateTo, onDateFromChange, onDateToChange, onApply, onReset, reports, onReportSelect, onExport, hideFilterButton, hideDateRange, showApplyButton, forecast, onForecastChange, onShare, showInsights, insightsActive, onInsightsClick, children, className, layout, fiscalStartYear, allTime, }: FilterBarProps): React.JSX.Element;
@@ -214,7 +214,7 @@ const fmtFiscalDay = (d) => `${String(d.getDate()).padStart(2, '0')} ${SHORT_MON
214
214
  // Fiscal-year-aware dropdown options. FY runs 1 Apr (fyStart) → 31 Mar (fyStart+1).
215
215
  // Weeks are W1..W52/53 counting 7-day spans from 1 Apr; months ordered Apr→Mar;
216
216
  // quarters Q1 Apr-Jun … Q4 Jan-Mar; year shown as "YYYY-YYYY".
217
- function buildTimeOptions(fyStart) {
217
+ function buildTimeOptions(fyStart, allTime = false) {
218
218
  const fyOpen = new Date(fyStart, 3, 1);
219
219
  const fyClose = new Date(fyStart + 1, 2, 31);
220
220
  // Monday-aligned weeks clamped to the FY: W1 = 1 Apr → first Sunday (partial),
@@ -242,7 +242,10 @@ function buildTimeOptions(fyStart) {
242
242
  { value: 'Q3', label: `Q3 (Oct – Dec ${fyStart})` },
243
243
  { value: 'Q4', label: `Q4 (Jan – Mar ${fyStart + 1})` },
244
244
  ];
245
- const years = [{ value: `${fyStart}`, label: `${fyStart}-${fyStart + 1}` }];
245
+ // This financial year and the two before it, newest first; "All Time" last where the host offers it.
246
+ const years = [0, 1, 2].map(n => ({ value: `${fyStart - n}`, label: `FY ${fyStart - n}-${String(fyStart - n + 1).slice(2)}` }));
247
+ if (allTime)
248
+ years.push({ value: 'all', label: 'All Time' });
246
249
  return [
247
250
  { value: 'Day', label: 'Day', dropdownOptions: DAY_OPTIONS, quickOptions: DAY_QUICK },
248
251
  { value: 'Week', label: 'Week', dropdownOptions: weeks, quickOptions: WEEK_QUICK },
@@ -318,11 +321,31 @@ function ForecastControl({ forecast, onForecastChange, timeFilter, onTimeFilterC
318
321
  ? 'text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))]'
319
322
  : '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: [_jsx(TrendingUp, { className: "w-4 h-4" }), forecast.enabled && !open && (_jsx("span", { className: "absolute -top-1 -right-1 w-2 h-2 bg-[rgb(var(--color-primary))] rounded-full" }))] }), open && (_jsxs("div", { className: 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: [_jsxs("div", { className: "px-3 py-2 border-b border-[rgb(var(--bd-default))] flex items-center justify-between gap-2", children: [_jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [_jsx("span", { className: "text-xs font-semibold text-[rgb(var(--fg-default))] shrink-0", children: t('Forecast') }), onTimeFilterChange ? (_jsx(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]" })) : (_jsxs("span", { className: "text-[10px] text-[rgb(var(--fg-muted))]", children: ["\u00B7 ", t(timeFilter)] }))] }), _jsx(Switch, { checked: forecast.enabled, onCheckedChange: (enabled) => onForecastChange({ ...forecast, enabled }) })] }), _jsxs("div", { className: cn('p-3 space-y-3', !forecast.enabled && 'opacity-50 pointer-events-none'), children: [_jsx(Tabs, { tabs: 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 && (_jsx("p", { className: "text-[10px] text-[rgb(var(--fg-muted))]", children: t(activeMethod.hint) })), _jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsxs("div", { children: [_jsx("label", { className: "text-[10px] font-semibold text-[rgb(var(--fg-muted))] uppercase tracking-wider mb-1 block", children: t('Based On Last') }), _jsx(Stepper, { value: forecast.period, min: limits.min, max: limits.max, onChange: setPeriod }), _jsx("p", { className: "mt-1 text-[10px] text-[rgb(var(--fg-muted))] truncate", children: resolvePeriodRange(timeFilter, forecast.period, -1) })] }), _jsxs("div", { children: [_jsx("label", { className: "text-[10px] font-semibold text-[rgb(var(--fg-muted))] uppercase tracking-wider mb-1 block", children: t('Forecast Ahead') }), _jsx(Stepper, { value: forecast.horizon, min: horizonLimits.min, max: horizonLimits.max, onChange: setHorizon }), _jsx("p", { className: "mt-1 text-[10px] text-[rgb(var(--fg-muted))] truncate", children: resolvePeriodRange(timeFilter, forecast.horizon, 1) })] })] })] })] }))] }));
320
323
  }
321
- export 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 }) {
324
+ export 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, }) {
322
325
  const [popoverOpen, setPopoverOpen] = useState(false);
323
326
  const popoverRef = useRef(null);
324
327
  const buttonRef = useRef(null);
325
- const timeOptions = useMemo(() => (fiscalStartYear ? buildTimeOptions(fiscalStartYear) : TIME_FILTER_OPTIONS), [fiscalStartYear]);
328
+ // The divider between the secondary toggle and the time tabs only makes sense while they share a
329
+ // line. When the row wraps (narrow screens, long toggle labels), it would be left hanging at the
330
+ // end of the first line, so it hides.
331
+ const toggleRef = useRef(null);
332
+ const timeTabsRef = useRef(null);
333
+ const [tabsWrapped, setTabsWrapped] = useState(false);
334
+ useEffect(() => {
335
+ const a = toggleRef.current;
336
+ const b = timeTabsRef.current;
337
+ if (!a || !b || typeof ResizeObserver === 'undefined')
338
+ return;
339
+ const check = () => setTabsWrapped(a.offsetTop !== b.offsetTop);
340
+ const ro = new ResizeObserver(check);
341
+ ro.observe(a);
342
+ ro.observe(b);
343
+ if (a.parentElement)
344
+ ro.observe(a.parentElement);
345
+ check();
346
+ return () => ro.disconnect();
347
+ }, [secondaryToggle, hideTimeFilter]);
348
+ const timeOptions = useMemo(() => (fiscalStartYear ? buildTimeOptions(fiscalStartYear, allTime) : TIME_FILTER_OPTIONS), [fiscalStartYear, allTime]);
326
349
  // Handle click outside. Ignore clicks landing inside a portalled dropdown/select
327
350
  // list (rendered to document.body, outside popoverRef) so selecting a filter
328
351
  // option doesn't close this popover before the value commits.
@@ -385,10 +408,10 @@ export function FilterBar({ title, contextLabel, secondaryToggle, timeFilter = '
385
408
  ...(filter === 'Year' && { years: selectedValues }),
386
409
  };
387
410
  onTimeFilterSelectionChange?.(newSelection);
388
- } }))] }), _jsxs("div", { className: "flex-1 text-center", children: [_jsx("h1", { className: "text-sm font-semibold text-[rgb(var(--fg-default))]", children: title }), contextLabel && (_jsx("span", { className: "text-xs text-[rgb(var(--fg-muted))]", children: contextLabel }))] }), _jsxs("div", { className: "flex items-center gap-3", children: [!hideFilterButton && (_jsxs("div", { className: "relative", children: [_jsxs("button", { ref: buttonRef, onClick: () => setPopoverOpen(!popoverOpen), className: 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: [_jsx(ListFilter, { className: "w-4 h-4" }), hasActiveFilters && !popoverOpen && (_jsx("span", { className: "absolute -top-1 -right-1 w-2 h-2 bg-[rgb(var(--color-primary))] rounded-full" }))] }), popoverOpen && (_jsxs("div", { ref: popoverRef, className: 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: [_jsxs("div", { className: "px-3 py-2 border-b border-[rgb(var(--bd-default))] flex items-center justify-between", children: [_jsx("span", { className: "text-xs font-semibold text-[rgb(var(--fg-default))]", children: "Filters" }), _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: _jsx(X, { className: "w-3 h-3 text-[rgb(var(--fg-muted))]" }) })] }), _jsxs("div", { className: "p-3 space-y-3", children: [filters.length > 0 && (_jsx("div", { className: "grid gap-3", style: { gridTemplateColumns: `repeat(${filters.length}, minmax(140px, 1fr))` }, children: filters.map((filter) => (_jsxs("div", { children: [_jsx("label", { className: "text-[10px] font-semibold text-[rgb(var(--fg-muted))] uppercase tracking-wider mb-1 block", children: filter.label }), _jsx(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 && (_jsx(DatePicker, { mode: "range", value: dateRangeValue, onChange: handleDateRangeChange, inline: true }))] })] }))] })), forecast && onForecastChange && (_jsx(ForecastControl, { forecast: forecast, onForecastChange: onForecastChange, timeFilter: timeFilter, onTimeFilterChange: onTimeFilterChange })), showApplyButton && (_jsx("button", { onClick: onApply, className: 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) && (_jsx(OverflowMenu, { reports: reports, onReportSelect: onReportSelect, onShare: onShare, onExport: onExport }))] })] }) }) }));
411
+ } }))] }), _jsxs("div", { className: "flex-1 text-center", children: [_jsx("h1", { className: "text-sm font-semibold text-[rgb(var(--fg-default))]", children: title }), contextLabel && (_jsx("span", { className: "text-xs text-[rgb(var(--fg-muted))]", children: contextLabel })), description && _jsx("p", { className: "mt-0.5 text-xs text-[rgb(var(--fg-muted))]", children: description })] }), _jsxs("div", { className: "flex items-center gap-3", children: [!hideFilterButton && (_jsxs("div", { className: "relative", children: [_jsxs("button", { ref: buttonRef, onClick: () => setPopoverOpen(!popoverOpen), className: 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: [_jsx(ListFilter, { className: "w-4 h-4" }), hasActiveFilters && !popoverOpen && (_jsx("span", { className: "absolute -top-1 -right-1 w-2 h-2 bg-[rgb(var(--color-primary))] rounded-full" }))] }), popoverOpen && (_jsxs("div", { ref: popoverRef, className: 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: [_jsxs("div", { className: "px-3 py-2 border-b border-[rgb(var(--bd-default))] flex items-center justify-between", children: [_jsx("span", { className: "text-xs font-semibold text-[rgb(var(--fg-default))]", children: "Filters" }), _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: _jsx(X, { className: "w-3 h-3 text-[rgb(var(--fg-muted))]" }) })] }), _jsxs("div", { className: "p-3 space-y-3", children: [filters.length > 0 && (_jsx("div", { className: "grid gap-3", style: { gridTemplateColumns: `repeat(${filters.length}, minmax(140px, 1fr))` }, children: filters.map((filter) => (_jsxs("div", { children: [_jsx("label", { className: "text-[10px] font-semibold text-[rgb(var(--fg-muted))] uppercase tracking-wider mb-1 block", children: filter.label }), _jsx(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 && (_jsx(DatePicker, { mode: "range", value: dateRangeValue, onChange: handleDateRangeChange, inline: true }))] })] }))] })), forecast && onForecastChange && (_jsx(ForecastControl, { forecast: forecast, onForecastChange: onForecastChange, timeFilter: timeFilter, onTimeFilterChange: onTimeFilterChange })), showApplyButton && (_jsx("button", { onClick: onApply, className: 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) && (_jsx(OverflowMenu, { reports: reports, onReportSelect: onReportSelect, onShare: onShare, onExport: onExport }))] })] }) }) }));
389
412
  }
390
413
  // Default layout: Title centered, controls on right
391
- return (_jsxs("header", { className: cn('bg-[rgb(var(--bg-surface))] border-b border-[rgb(var(--bd-default))]', 'px-4 py-3 lg:px-6', className), children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("div", { className: "flex-1 min-w-0 flex items-center gap-2.5", children: [_jsx("h1", { className: "text-base sm:text-xl font-bold text-[rgb(var(--fg-default))] leading-tight truncate", children: title }), contextLabel && (_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: [_jsx(CalendarDays, { className: "w-3.5 h-3.5" }), contextLabel] }))] }), _jsxs("div", { className: "flex items-center gap-3", children: [secondaryToggle && (_jsxs(_Fragment, { children: [_jsx("div", { className: "hidden sm:block", children: _jsx(Tabs, { tabs: secondaryToggle.options.map(opt => ({ id: opt.value, label: opt.label })), activeTab: secondaryToggle.value, onTabChange: secondaryToggle.onChange, size: "sm" }) }), _jsx("div", { className: "hidden sm:block h-4 w-px bg-[rgb(var(--bd-default))] self-center" })] })), !hideTimeFilter && (_jsx("div", { className: "hidden sm:block", children: _jsx(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: {
414
+ return (_jsxs("header", { className: cn('bg-[rgb(var(--bg-surface))] border-b border-[rgb(var(--bd-default))]', 'px-4 py-3 lg:px-6', className), children: [_jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-2", children: [_jsxs("div", { className: "flex-auto min-w-0 flex items-center gap-2.5", children: [_jsx("h1", { className: "text-base sm:text-xl font-bold text-[rgb(var(--fg-default))] leading-tight truncate", children: title }), contextLabel && (_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: [_jsx(CalendarDays, { className: "w-3.5 h-3.5" }), contextLabel] }))] }), _jsxs("div", { className: "ml-auto flex flex-wrap items-center justify-end gap-x-3 gap-y-2", children: [secondaryToggle && (_jsxs(_Fragment, { children: [_jsx("div", { ref: toggleRef, className: "hidden sm:block", children: _jsx(Tabs, { tabs: secondaryToggle.options.map(opt => ({ id: opt.value, label: opt.label })), activeTab: secondaryToggle.value, onTabChange: secondaryToggle.onChange, size: "sm" }) }), _jsx("div", { className: cn('hidden h-4 w-px bg-[rgb(var(--bd-default))] self-center', !tabsWrapped && 'sm:block') })] })), !hideTimeFilter && (_jsx("div", { ref: timeTabsRef, className: "hidden sm:block", children: _jsx(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: {
392
415
  Day: timeFilterSelection?.days || [],
393
416
  Week: timeFilterSelection?.weeks || [],
394
417
  Month: timeFilterSelection?.months || [],
@@ -407,7 +430,7 @@ export function FilterBar({ title, contextLabel, secondaryToggle, timeFilter = '
407
430
  onTimeFilterSelectionChange?.(newSelection);
408
431
  } }) })), _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 && (_jsxs("div", { className: "relative", children: [_jsxs("button", { ref: buttonRef, onClick: () => setPopoverOpen(!popoverOpen), className: cn('w-7 h-7 rounded-md flex items-center justify-center transition-colors relative', popoverOpen
409
432
  ? 'bg-[rgb(var(--color-primary))] text-white shadow-sm'
410
- : 'text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))]'), title: "Filters", children: [_jsx(ListFilter, { className: "w-4 h-4" }), hasActiveFilters && !popoverOpen && (_jsx("span", { className: "absolute -top-1 -right-1 w-2 h-2 bg-[rgb(var(--color-primary))] rounded-full" }))] }), popoverOpen && (_jsxs("div", { ref: popoverRef, className: 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: [_jsxs("div", { className: "px-3 py-2 border-b border-[rgb(var(--bd-default))] flex items-center justify-between", children: [_jsx("span", { className: "text-xs font-semibold text-[rgb(var(--fg-default))]", children: "Filters" }), _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: _jsx(X, { className: "w-3 h-3 text-[rgb(var(--fg-muted))]" }) })] }), _jsxs("div", { className: "p-3 space-y-3", children: [filters.length > 0 && (_jsx("div", { className: "grid gap-3", style: { gridTemplateColumns: `repeat(${Math.min(filters.length, 3)}, minmax(140px, 1fr))` }, children: filters.map((filter) => (_jsxs("div", { children: [_jsx("label", { className: "text-[10px] font-semibold text-[rgb(var(--fg-muted))] uppercase tracking-wider mb-1 block", children: filter.label }), _jsx(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 && (_jsx(DatePicker, { mode: "range", value: dateRangeValue, onChange: handleDateRangeChange, inline: true }))] })] }))] })), forecast && onForecastChange && (_jsx(ForecastControl, { forecast: forecast, onForecastChange: onForecastChange, timeFilter: timeFilter, onTimeFilterChange: onTimeFilterChange, bare: true })), (showInsights || reports.length > 0 || onShare || onExport) && (_jsx(OverflowMenu, { showInsights: showInsights, insightsActive: insightsActive, onInsightsClick: onInsightsClick, reports: reports, onReportSelect: onReportSelect, onShare: onShare, onExport: onExport }))] }), showApplyButton && (_jsx("button", { onClick: onApply, className: 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 && (_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: [_jsx(CalendarDays, { className: "w-3.5 h-3.5" }), contextLabel] })), (!hideTimeFilter || secondaryToggle) && (_jsxs("div", { className: "sm:hidden mt-2 flex items-center gap-2 overflow-x-auto", children: [secondaryToggle && (_jsx(Tabs, { tabs: secondaryToggle.options.map(opt => ({ id: opt.value, label: opt.label })), activeTab: secondaryToggle.value, onTabChange: secondaryToggle.onChange, size: "sm" })), !hideTimeFilter && (_jsx(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: {
433
+ : 'text-[rgb(var(--fg-muted))] hover:text-[rgb(var(--fg-default))] hover:bg-[rgb(var(--bg-subtle))]'), title: "Filters", children: [_jsx(ListFilter, { className: "w-4 h-4" }), hasActiveFilters && !popoverOpen && (_jsx("span", { className: "absolute -top-1 -right-1 w-2 h-2 bg-[rgb(var(--color-primary))] rounded-full" }))] }), popoverOpen && (_jsxs("div", { ref: popoverRef, className: 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: [_jsxs("div", { className: "px-3 py-2 border-b border-[rgb(var(--bd-default))] flex items-center justify-between", children: [_jsx("span", { className: "text-xs font-semibold text-[rgb(var(--fg-default))]", children: "Filters" }), _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: _jsx(X, { className: "w-3 h-3 text-[rgb(var(--fg-muted))]" }) })] }), _jsxs("div", { className: "p-3 space-y-3", children: [filters.length > 0 && (_jsx("div", { className: "grid gap-3", style: { gridTemplateColumns: `repeat(${Math.min(filters.length, 3)}, minmax(140px, 1fr))` }, children: filters.map((filter) => (_jsxs("div", { children: [_jsx("label", { className: "text-[10px] font-semibold text-[rgb(var(--fg-muted))] uppercase tracking-wider mb-1 block", children: filter.label }), _jsx(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 && (_jsx(DatePicker, { mode: "range", value: dateRangeValue, onChange: handleDateRangeChange, inline: true }))] })] }))] })), forecast && onForecastChange && (_jsx(ForecastControl, { forecast: forecast, onForecastChange: onForecastChange, timeFilter: timeFilter, onTimeFilterChange: onTimeFilterChange, bare: true })), (showInsights || reports.length > 0 || onShare || onExport) && (_jsx(OverflowMenu, { showInsights: showInsights, insightsActive: insightsActive, onInsightsClick: onInsightsClick, reports: reports, onReportSelect: onReportSelect, onShare: onShare, onExport: onExport }))] }), showApplyButton && (_jsx("button", { onClick: onApply, className: 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 && (_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: [_jsx(CalendarDays, { className: "w-3.5 h-3.5" }), contextLabel] })), (!hideTimeFilter || secondaryToggle) && (_jsxs("div", { className: "sm:hidden mt-2 flex flex-col items-start gap-2", children: [secondaryToggle && (_jsx(Tabs, { tabs: secondaryToggle.options.map(opt => ({ id: opt.value, label: opt.label })), activeTab: secondaryToggle.value, onTabChange: secondaryToggle.onChange, size: "sm" })), !hideTimeFilter && (_jsx(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: {
411
434
  Day: timeFilterSelection?.days || [],
412
435
  Week: timeFilterSelection?.weeks || [],
413
436
  Month: timeFilterSelection?.months || [],
@@ -424,5 +447,5 @@ export function FilterBar({ title, contextLabel, secondaryToggle, timeFilter = '
424
447
  ...(filter === 'Year' && { years: selectedValues }),
425
448
  };
426
449
  onTimeFilterSelectionChange?.(newSelection);
427
- } }))] }))] }));
450
+ } }))] })), description && _jsx("p", { className: "mt-1 text-sm text-[rgb(var(--fg-muted))]", children: description })] }));
428
451
  }
@@ -1,4 +1,3 @@
1
- 'use client';
2
1
  // ECharts Components
3
2
  export { AreaChart } from './Area.js';
4
3
  export { BarChart } from './Bar.js';
@@ -1,4 +1,3 @@
1
- 'use client';
2
1
  // Main Components
3
2
  export { FilterBar, computeForecast, projectForecast, FORECAST_METHODS } from './FilterBar.js';
4
3
  export { DashboardTooltip } from './Tooltip.js';
@@ -1,4 +1,3 @@
1
- 'use client';
2
1
  // KPI Components
3
2
  export { KpiShell, ACCENT_MAP } from './kpi-shell.js';
4
3
  export { KpiComparison } from './KpiComparison.js';
@@ -5,7 +5,18 @@ export declare const fiscalWeekRange: (fyStart: number, wk: number) => {
5
5
  to: Date;
6
6
  } | null;
7
7
  export declare const fiscalWeekCount: (fyStart: number) => number;
8
- export declare const rangeFromSelection: (sel: TimeFilterSelection | undefined, fyStart: number) => {
8
+ /**
9
+ * The dates a selection covers.
10
+ *
11
+ * Quick picks (This / Last …) count from `today`: "This week" is the week holding today, "Last
12
+ * month" the calendar month before this one, "This quarter" the fiscal quarter holding today.
13
+ * They used to anchor to the financial year's first period, so "This week" meant Week 1 in April.
14
+ * A "This …" range runs to today, since nothing after today has happened yet.
15
+ *
16
+ * A numbered pick (W27, month "9", Q3, a year "2025") is that period of the financial year
17
+ * starting `fyStart`. Year "all" is all time, from the earliest date a book could hold to today.
18
+ */
19
+ export declare const rangeFromSelection: (sel: TimeFilterSelection | undefined, fyStart: number, today?: Date) => {
9
20
  from: Date;
10
21
  to: Date;
11
22
  };
@@ -37,44 +37,77 @@ export const fiscalWeekCount = (fyStart) => {
37
37
  wk++;
38
38
  return wk;
39
39
  };
40
- // "this" anchors to the FY's first period (W1 / Apr / Q1), per fiscal reporting.
41
- export const rangeFromSelection = (sel, fyStart) => {
40
+ const startOfDay = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate());
41
+ const addDays = (d, n) => new Date(d.getFullYear(), d.getMonth(), d.getDate() + n);
42
+ // Monday of the week holding `d` (weeks run Monday to Sunday, as the fiscal weeks do).
43
+ const mondayOf = (d) => addDays(d, -((d.getDay() + 6) % 7));
44
+ /**
45
+ * The dates a selection covers.
46
+ *
47
+ * Quick picks (This / Last …) count from `today`: "This week" is the week holding today, "Last
48
+ * month" the calendar month before this one, "This quarter" the fiscal quarter holding today.
49
+ * They used to anchor to the financial year's first period, so "This week" meant Week 1 in April.
50
+ * A "This …" range runs to today, since nothing after today has happened yet.
51
+ *
52
+ * A numbered pick (W27, month "9", Q3, a year "2025") is that period of the financial year
53
+ * starting `fyStart`. Year "all" is all time, from the earliest date a book could hold to today.
54
+ */
55
+ export const rangeFromSelection = (sel, fyStart, today = new Date()) => {
56
+ const t = startOfDay(today);
42
57
  const fyOpen = new Date(fyStart, 3, 1);
43
58
  const fyClose = new Date(fyStart + 1, 2, 31);
44
59
  const yearOf = (mo) => (mo >= 3 ? fyStart : fyStart + 1);
45
60
  const monthRange = (mo) => ({ from: new Date(yearOf(mo), mo, 1), to: new Date(yearOf(mo), mo + 1, 0) });
46
61
  const type = sel?.type || 'Month';
47
62
  if (type === 'Day') {
48
- const now = new Date();
49
- const y = now.getFullYear(), m = now.getMonth(), d = now.getDate();
50
63
  const pick = sel?.days?.[0] || 'today';
51
- if (pick === 'yesterday') {
52
- const dt = new Date(y, m, d - 1);
53
- return { from: dt, to: dt };
54
- }
55
- if (pick === 'last7')
56
- return { from: new Date(y, m, d - 6), to: new Date(y, m, d) };
57
- if (pick === 'last14')
58
- return { from: new Date(y, m, d - 13), to: new Date(y, m, d) };
59
- if (pick === 'last30')
60
- return { from: new Date(y, m, d - 29), to: new Date(y, m, d) };
61
- return { from: new Date(y, m, d), to: new Date(y, m, d) };
64
+ if (pick === 'yesterday')
65
+ return { from: addDays(t, -1), to: addDays(t, -1) };
66
+ const span = { last7: 7, last14: 14, last30: 30 }[pick];
67
+ return span ? { from: addDays(t, -(span - 1)), to: t } : { from: t, to: t };
62
68
  }
63
69
  if (type === 'Week') {
64
- const pick = sel?.weeks?.[0] || 'W1';
70
+ const pick = sel?.weeks?.[0] || 'this';
71
+ const monday = mondayOf(t);
72
+ if (pick === 'this')
73
+ return { from: monday, to: t };
74
+ if (pick === 'last')
75
+ return { from: addDays(monday, -7), to: addDays(monday, -1) };
76
+ // "Last 2 weeks" is this week and the one before it, to today.
77
+ const weeks = { last2: 2, last4: 4 }[pick];
78
+ if (weeks)
79
+ return { from: addDays(monday, -7 * (weeks - 1)), to: t };
65
80
  const wk = /^W\d+$/i.test(pick) ? parseInt(pick.slice(1)) : 1;
66
81
  return fiscalWeekRange(fyStart, wk) || { from: fyOpen, to: fyClose };
67
82
  }
68
83
  if (type === 'Quarter') {
69
- const pick = sel?.quarters?.[0];
70
- const qNum = pick && /^q[1-4]$/i.test(pick) ? parseInt(pick.slice(1)) - 1 : 0;
84
+ const pick = sel?.quarters?.[0] || 'this';
85
+ // The fiscal quarter holding today: Apr–Jun is Q1.
86
+ const qStart = new Date(t.getFullYear(), t.getMonth() - (((t.getMonth() - 3 + 12) % 12) % 3), 1);
87
+ if (pick === 'this')
88
+ return { from: qStart, to: t };
89
+ if (pick === 'last')
90
+ return { from: new Date(qStart.getFullYear(), qStart.getMonth() - 3, 1), to: addDays(qStart, -1) };
91
+ const qNum = /^q[1-4]$/i.test(pick) ? parseInt(pick.slice(1)) - 1 : 0;
71
92
  const startMonth = (3 + qNum * 3) % 12;
72
93
  const sy = yearOf(startMonth);
73
94
  return { from: new Date(sy, startMonth, 1), to: new Date(sy, startMonth + 3, 0) };
74
95
  }
75
- if (type === 'Year')
76
- return { from: fyOpen, to: fyClose };
77
- const picks = (sel?.months || []).filter(s => /^\d{1,2}$/.test(s)).map(s => parseInt(s));
96
+ if (type === 'Year') {
97
+ const pick = sel?.years?.[0] || 'this';
98
+ if (pick === 'all')
99
+ return { from: new Date(1970, 0, 1), to: t };
100
+ if (pick === 'last')
101
+ return { from: new Date(fyStart - 1, 3, 1), to: new Date(fyStart, 2, 31) };
102
+ const fy = /^\d{4}$/.test(pick) ? parseInt(pick) : fyStart;
103
+ return { from: new Date(fy, 3, 1), to: new Date(fy + 1, 2, 31) };
104
+ }
105
+ const months = sel?.months || [];
106
+ if (months.includes('this'))
107
+ return { from: new Date(t.getFullYear(), t.getMonth(), 1), to: t };
108
+ if (months.includes('last'))
109
+ return { from: new Date(t.getFullYear(), t.getMonth() - 1, 1), to: new Date(t.getFullYear(), t.getMonth(), 0) };
110
+ const picks = months.filter(s => /^\d{1,2}$/.test(s)).map(s => parseInt(s));
78
111
  if (picks.length > 0) {
79
112
  const ranges = picks.map(monthRange);
80
113
  return {
@@ -82,7 +115,7 @@ export const rangeFromSelection = (sel, fyStart) => {
82
115
  to: ranges.reduce((a, b) => (b.to > a.to ? b : a)).to,
83
116
  };
84
117
  }
85
- return monthRange(3);
118
+ return { from: new Date(t.getFullYear(), t.getMonth(), 1), to: t };
86
119
  };
87
120
  // Header subtitle for the selected period. Week/Day show their day span in brackets;
88
121
  // Quarter/Year show fiscal labels. `t` translates the period words.
@@ -99,17 +132,28 @@ export const buildContextLabel = (sel, fyStart, dateFrom, dateTo, t) => {
99
132
  return `${pick} (${dayRange})`;
100
133
  }
101
134
  if (!dateFrom && !dateTo && sel?.type === 'Week') {
102
- const w = sel.weeks?.[0];
103
- const wk = w && /^W\d+$/i.test(w) ? w.slice(1) : '1';
135
+ const w = sel.weeks?.[0] || 'this';
136
+ const quick = { this: 'This Week', last: 'Last Week', last2: 'Last 2 Weeks', last4: 'Last 4 Weeks' }[w];
137
+ if (quick)
138
+ return `${t(quick)} (${dayRange})`;
139
+ const wk = /^W\d+$/i.test(w) ? w.slice(1) : '1';
104
140
  return `${t('Week')} ${wk} (${dayRange})`;
105
141
  }
106
142
  if (!dateFrom && !dateTo && sel?.type === 'Quarter') {
107
- const q = sel.quarters?.[0];
108
- const label = q && /^q[1-4]$/i.test(q) ? `Q${q.slice(1)}` : 'Q1';
143
+ const q = sel.quarters?.[0] || 'this';
144
+ const label = q === 'this' ? t('This Quarter') : q === 'last' ? t('Last Quarter') : /^q[1-4]$/i.test(q) ? `Q${q.slice(1)}` : 'Q1';
109
145
  return `${label} (${fmtMon(from)} – ${fmtMon(to)})`;
110
146
  }
111
147
  if (!dateFrom && !dateTo && sel?.type === 'Year') {
112
- return `${t('FY')} ${fyStart}-${fyStart + 1}`;
148
+ const y = sel.years?.[0] || 'this';
149
+ if (y === 'all')
150
+ return t('All Time');
151
+ const fy = y === 'last' ? fyStart - 1 : /^\d{4}$/.test(y) ? parseInt(y) : fyStart;
152
+ return `${t('FY')} ${fy}-${fy + 1}`;
153
+ }
154
+ const m0 = sel?.months?.[0];
155
+ if (!dateFrom && !dateTo && sel?.type === 'Month' && (m0 === 'this' || m0 === 'last')) {
156
+ return `${t(m0 === 'this' ? 'This Month' : 'Last Month')} (${dayRange})`;
113
157
  }
114
158
  const sameMonth = from.getFullYear() === to.getFullYear() && from.getMonth() === to.getMonth();
115
159
  if (sameMonth)
@@ -1,2 +1 @@
1
- 'use client';
2
1
  export { BooleanCell } from './BooleanCell.js';
@@ -1,2 +1 @@
1
- 'use client';
2
1
  export { GroupingSection } from './GroupingSection.js';
@@ -1,4 +1,3 @@
1
- 'use client';
2
1
  export { ActionsMenu } from './ActionsMenu.js';
3
2
  export { ViewSection } from './view/ViewSection.js';
4
3
  export { ExportSection } from './export/ExportSection.js';
@@ -1,4 +1,3 @@
1
- 'use client';
2
1
  // Main component
3
2
  export { DataGrid } from './DataGrid.js';
4
3
  // Controls
@@ -1,4 +1,3 @@
1
- 'use client';
2
1
  // DatePicker - Single/Range date selection without time
3
2
  export { DatePicker } from './DatePicker.js';
4
3
  // DateTimePicker - Single date WITH time selection
@@ -1,2 +1 @@
1
- 'use client';
2
1
  export { Dropdown } from './Dropdown.js';
@@ -1,2 +1 @@
1
- 'use client';
2
1
  export { FileAttachment } from './FileAttachment.js';
@@ -1,4 +1,3 @@
1
- 'use client';
2
1
  // Form controls entry: `import { DatePicker } from 'indas-ui/forms'`
3
2
  export { Dropdown } from './dropdown/index.js';
4
3
  export { DatePicker, DateTimePicker } from './date-picker/index.js';
@@ -1,2 +1 @@
1
- 'use client';
2
1
  export { RemarkInput, RemarkTrail, commitRemarkToTrail } from './RemarkInput.js';
@@ -1,4 +1,3 @@
1
- 'use client';
2
1
  // Main component index - exports all design system components
3
2
  export * from './ui/index.js';
4
3
  export * from './layout/index.js';
@@ -1,4 +1,3 @@
1
- 'use client';
2
1
  // Layout Components - Centralized exports
3
2
  export { DynamicSidebar } from './sidebar.js';
4
3
  export { Footer, CompactFooter } from './footer.js';
@@ -13,6 +13,7 @@ import '../../styles/sidebar-scrollbar.css';
13
13
  import { createPortal } from 'react-dom';
14
14
  import { SidebarContextMenu, SidebarGroupContextMenu } from './SidebarContextMenu.js';
15
15
  import { useSidebarPreferences } from '../../contexts/SidebarPreferencesContext.js';
16
+ import { useOptionalSessionAdapter } from '../../contexts/SessionAdapterContext.js';
16
17
  // Route mapping imported from shared utility: getModuleRoutePath
17
18
  // Routes that support quick-create via sidebar "+" button.
18
19
  // When clicked, navigates to {route}?create=true — the page opens its create modal.
@@ -123,6 +124,14 @@ export function DynamicSidebar({ isCollapsed = false, onMenuItemClick, onToggleS
123
124
  const [expandedSubModules, setExpandedSubModules] = React.useState([]);
124
125
  const [loading, setLoading] = React.useState(true);
125
126
  const [error, setError] = React.useState(null);
127
+ // The menu API needs the signed-in session (company credentials). Read from the session adapter,
128
+ // not next-auth, so the library does not require next-auth; held in a ref so a fresh session
129
+ // object does not refetch the menu, only the session arriving or leaving does.
130
+ const sessionAdapter = useOptionalSessionAdapter();
131
+ const sessionRef = React.useRef(sessionAdapter?.data ?? null);
132
+ sessionRef.current = sessionAdapter?.data ?? null;
133
+ const hasSession = Boolean(sessionAdapter?.data);
134
+ const sessionStatus = sessionAdapter?.status ?? 'unauthenticated';
126
135
  const [needsScroll, setNeedsScroll] = React.useState(false);
127
136
  const navRef = React.useRef(null);
128
137
  const [hoveredGroup, setHoveredGroup] = React.useState(null);
@@ -164,6 +173,12 @@ export function DynamicSidebar({ isCollapsed = false, onMenuItemClick, onToggleS
164
173
  if (!companyId || !userId) {
165
174
  return;
166
175
  }
176
+ // No session yet: wait for it (this runs again when it arrives); none at all: no menu.
177
+ if (!hasSession) {
178
+ if (sessionStatus !== 'loading')
179
+ setLoading(false);
180
+ return;
181
+ }
167
182
  let isCancelled = false;
168
183
  const fetchNavigation = async () => {
169
184
  // Check cache first - include session start time to invalidate cache across logins
@@ -189,7 +204,7 @@ export function DynamicSidebar({ isCollapsed = false, onMenuItemClick, onToggleS
189
204
  try {
190
205
  setLoading(true);
191
206
  setError(null);
192
- const navigation = await getDynamicNavigation(companyId, userId);
207
+ const navigation = await getDynamicNavigation(companyId, userId, sessionRef.current);
193
208
  if (!isCancelled) {
194
209
  setGroupedModules(navigation);
195
210
  // Cache the result
@@ -217,7 +232,7 @@ export function DynamicSidebar({ isCollapsed = false, onMenuItemClick, onToggleS
217
232
  clearTimeout(hoverTimeoutRef.current);
218
233
  }
219
234
  };
220
- }, [companyId, userId]);
235
+ }, [companyId, userId, hasSession, sessionStatus]);
221
236
  // Auto-open parent items based on current path (optimized)
222
237
  React.useEffect(() => {
223
238
  if (groupedModules.length === 0)
@@ -1,4 +1,3 @@
1
- 'use client';
2
1
  /**
3
2
  * Unified Editable Cell Components
4
3
  *
@@ -1,3 +1,2 @@
1
- 'use client';
2
1
  // Editable Cells
3
2
  export { EditableCell, EditableTextCell, EditableDecimalCell } from './editable-cells/index.js';
@@ -5,14 +5,14 @@ import { useRouter } from 'next/navigation';
5
5
  import { MessageSquare, Send, RefreshCw } from 'lucide-react';
6
6
  import { cn } from '../../lib/utils.js';
7
7
  import { useLanguage } from '../../contexts/LanguageContext.js';
8
- import { useSession } from 'next-auth/react';
8
+ import { useOptionalSessionAdapter } from '../../contexts/SessionAdapterContext.js';
9
9
  import { useMessaging } from '../../contexts/MessagingContext.js';
10
10
  import { parseParticipants } from '../../lib/api/activity/messaging/messaging.js';
11
11
  import { getAvatarColor, getInitials, formatRelativeTime } from './conversation-list.js';
12
12
  import { Button } from '../ui/index.js';
13
13
  export function MessagingPanelContent({ onClose }) {
14
14
  const { t } = useLanguage();
15
- const { data: session } = useSession();
15
+ const session = useOptionalSessionAdapter()?.data ?? null;
16
16
  const { state, actions } = useMessaging();
17
17
  const router = useRouter();
18
18
  const currentUserId = String(session?.user?.UserID || session?.user?.userID || '');
@@ -1,4 +1,3 @@
1
- 'use client';
2
1
  // Modal components exports
3
2
  export { APIConfigModal } from './api-config-modal.js';
4
3
  export { GlobalAPIConfigModal } from './global-api-config-modal.js';
@@ -1,7 +1,7 @@
1
1
  import * as React from "react";
2
2
  import { type VariantProps } from "class-variance-authority";
3
3
  declare const badgeVariants: (props?: {
4
- variant?: "secondary" | "outline" | "destructive" | "warning" | "success" | "orange" | "cyan" | "indigo" | "lime" | "pink" | "purple" | "teal" | "violet" | "default" | "department" | "module" | "danger" | "info" | "amber" | "emerald" | "sky" | "rose" | "primary-selected" | "warning-selected" | "success-selected" | "orange-selected" | "error-selected" | "primary-unselected" | "warning-unselected" | "success-unselected" | "orange-unselected" | "error-unselected";
4
+ variant?: "secondary" | "outline" | "destructive" | "warning" | "success" | "orange" | "cyan" | "indigo" | "lime" | "pink" | "purple" | "teal" | "violet" | "default" | "module" | "info" | "department" | "danger" | "amber" | "emerald" | "sky" | "rose" | "primary-selected" | "warning-selected" | "success-selected" | "orange-selected" | "error-selected" | "primary-unselected" | "warning-unselected" | "success-unselected" | "orange-unselected" | "error-unselected";
5
5
  size?: "sm" | "lg" | "default";
6
6
  } & import("class-variance-authority/types").ClassProp) => string;
7
7
  export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {
@@ -1,4 +1,3 @@
1
- 'use client';
2
1
  // UI Components - Centralized exports with organized folder structure
3
2
  // ============================================================================
4
3
  // FORMS
@@ -38,7 +38,10 @@ export function Tabs({ tabs, activeTab, onTabChange, fullWidth = false, classNam
38
38
  ? 'bg-[rgb(var(--color-primary))] text-white border-[rgb(var(--color-primary))]'
39
39
  : '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));
40
40
  }) })) : undefined;
41
- return (_jsx(Dropdown, { options: tab.dropdownOptions, value: selected, onValueChange: (sel) => {
41
+ return (_jsx(Dropdown, { options: tab.dropdownOptions,
42
+ // Periods are listed in time order (Apr, May, Jun… / Week 1, 2, 3…); sorted by label,
43
+ // the months came out Apr, Aug, Dec, Feb.
44
+ preserveOrder: true, value: selected, onValueChange: (sel) => {
42
45
  const arr = Array.isArray(sel) ? sel.map(String) : [String(sel)];
43
46
  // Ticking a named option clears any quick preset (This/Last) — they're exclusive.
44
47
  const quickVals = (tab.quickOptions || []).map(q => q.value);