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.
- package/dist/auth.d.ts +1 -0
- package/dist/auth.js +5 -0
- package/dist/cjs/auth.js +10 -0
- package/dist/cjs/components/chat/chat.js +2 -2
- package/dist/cjs/components/dashboard/FilterBar.js +31 -8
- package/dist/cjs/components/dashboard/time-range.js +71 -27
- package/dist/cjs/components/layout/sidebar.js +17 -2
- package/dist/cjs/components/messaging/messaging-panel-content.js +2 -2
- package/dist/cjs/components/ui/navigation/tabs.js +4 -1
- package/dist/cjs/contexts/EmailContext.js +3 -2
- package/dist/cjs/contexts/MessagingContext.js +2 -2
- package/dist/cjs/contexts/NavigationContext.js +17 -2
- package/dist/cjs/contexts/NotificationContext.js +2 -2
- package/dist/cjs/contexts/SessionAdapterContext.js +8 -0
- package/dist/cjs/hooks/useApproval.js +2 -2
- package/dist/cjs/hooks/useBusinessAudit.js +2 -2
- package/dist/cjs/hooks/useDraft.js +2 -2
- package/dist/cjs/lib/api/utility/menu.js +8 -7
- package/dist/cjs/providers.js +3 -3
- package/dist/components/auth/index.js +0 -1
- package/dist/components/chat/chat.js +2 -2
- package/dist/components/chat/index.js +0 -1
- package/dist/components/dashboard/FilterBar.d.ts +5 -1
- package/dist/components/dashboard/FilterBar.js +31 -8
- package/dist/components/dashboard/charts/index.js +0 -1
- package/dist/components/dashboard/index.js +0 -1
- package/dist/components/dashboard/kpi/index.js +0 -1
- package/dist/components/dashboard/time-range.d.ts +12 -1
- package/dist/components/dashboard/time-range.js +71 -27
- package/dist/components/datagrid/cells/index.js +0 -1
- package/dist/components/datagrid/controls/menu/grouping/index.js +0 -1
- package/dist/components/datagrid/controls/menu/index.js +0 -1
- package/dist/components/datagrid/index.js +0 -1
- package/dist/components/forms/date-picker/index.js +0 -1
- package/dist/components/forms/dropdown/index.js +0 -1
- package/dist/components/forms/file-attachment/index.js +0 -1
- package/dist/components/forms/index.js +0 -1
- package/dist/components/forms/remark-input/index.js +0 -1
- package/dist/components/index.js +0 -1
- package/dist/components/layout/index.js +0 -1
- package/dist/components/layout/sidebar.js +17 -2
- package/dist/components/master/editable-cells/index.js +0 -1
- package/dist/components/master/index.js +0 -1
- package/dist/components/messaging/messaging-panel-content.js +2 -2
- package/dist/components/modals/index.js +0 -1
- package/dist/components/ui/feedback/badge.d.ts +1 -1
- package/dist/components/ui/index.js +0 -1
- package/dist/components/ui/navigation/tabs.js +4 -1
- package/dist/contexts/EmailContext.js +3 -2
- package/dist/contexts/MessagingContext.js +2 -2
- package/dist/contexts/NavigationContext.js +17 -2
- package/dist/contexts/NotificationContext.js +2 -2
- package/dist/contexts/SessionAdapterContext.d.ts +5 -0
- package/dist/contexts/SessionAdapterContext.js +7 -0
- package/dist/hooks/index.js +0 -1
- package/dist/hooks/useApproval.js +2 -2
- package/dist/hooks/useBusinessAudit.js +2 -2
- package/dist/hooks/useDraft.js +2 -2
- package/dist/index.js +0 -1
- package/dist/lib/api/activity/audit/index.js +0 -1
- package/dist/lib/api/activity/email/index.js +0 -1
- package/dist/lib/api/activity/index.js +0 -1
- package/dist/lib/api/activity/notifications/index.js +0 -1
- package/dist/lib/api/activity/remarks/index.js +0 -1
- package/dist/lib/api/approval/index.js +0 -1
- package/dist/lib/api/auth/index.js +0 -1
- package/dist/lib/api/core/index.js +0 -1
- package/dist/lib/api/draft/index.js +0 -1
- package/dist/lib/api/enquiry/index.js +0 -1
- package/dist/lib/api/estimation/index.js +0 -1
- package/dist/lib/api/index.js +0 -1
- package/dist/lib/api/master/index.js +0 -1
- package/dist/lib/api/utility/index.js +0 -1
- package/dist/lib/api/utility/menu.d.ts +7 -3
- package/dist/lib/api/utility/menu.js +8 -7
- package/dist/providers.d.ts +0 -1
- package/dist/providers.js +2 -2
- 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';
|
package/dist/cjs/auth.js
ADDED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
47
|
-
const
|
|
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
|
-
|
|
59
|
-
|
|
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] || '
|
|
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
|
-
|
|
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
|
-
|
|
83
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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);
|