@m5kdev/web-ui 0.8.9 → 0.8.10

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 (25) hide show
  1. package/dist/packages/backend/dist/src/modules/auth/auth.dto.d.mts +5 -5
  2. package/dist/packages/backend/dist/src/modules/auth/auth.dto.d.ts +5 -5
  3. package/dist/packages/backend/dist/src/types.d.mts +12 -12
  4. package/dist/packages/backend/dist/src/types.d.ts +12 -12
  5. package/dist/src/modules/auth/components/ForgotPasswordForm.js +1 -1
  6. package/dist/src/modules/auth/components/ForgotPasswordForm.mjs +1 -1
  7. package/dist/src/modules/auth/components/LoginForm.js +1 -1
  8. package/dist/src/modules/auth/components/LoginForm.mjs +1 -1
  9. package/dist/src/modules/auth/components/ProfileRoute.js +1 -1
  10. package/dist/src/modules/auth/components/ProfileRoute.mjs +1 -1
  11. package/dist/src/modules/auth/components/ResetPasswordForm.js +1 -1
  12. package/dist/src/modules/auth/components/ResetPasswordForm.mjs +1 -1
  13. package/dist/src/modules/auth/components/SignupFormRoute.js +1 -1
  14. package/dist/src/modules/auth/components/SignupFormRoute.mjs +1 -1
  15. package/dist/src/modules/table/components/NuqsTable.js +1 -1
  16. package/dist/src/modules/table/components/NuqsTable.mjs +1 -1
  17. package/dist/src/modules/table/components/TableFiltering.js +11 -1
  18. package/dist/src/modules/table/components/TableFiltering.js.map +1 -1
  19. package/dist/src/modules/table/components/TableFiltering.mjs +11 -1
  20. package/dist/src/modules/table/components/TableFiltering.mjs.map +1 -1
  21. package/dist/src/modules/table/filterTransformers.js +20 -1
  22. package/dist/src/modules/table/filterTransformers.js.map +1 -1
  23. package/dist/src/modules/table/filterTransformers.mjs +20 -1
  24. package/dist/src/modules/table/filterTransformers.mjs.map +1 -1
  25. package/package.json +5 -5
@@ -15,9 +15,9 @@ declare const waitlistSchema: z.ZodObject<{
15
15
  declare const waitlistOutputSchema: z.ZodObject<{
16
16
  id: z.ZodString;
17
17
  name: z.ZodNullable<z.ZodString>;
18
- email: z.ZodNullable<z.ZodString>;
19
18
  createdAt: z.ZodDate;
20
19
  updatedAt: z.ZodNullable<z.ZodDate>;
20
+ email: z.ZodNullable<z.ZodString>;
21
21
  status: z.ZodString;
22
22
  }, z.core.$strip>;
23
23
  type WaitlistOutput = z.infer<typeof waitlistOutputSchema>;
@@ -35,9 +35,9 @@ declare const accountClaimSchema: z.ZodObject<{
35
35
  }, z.core.$strip>;
36
36
  declare const accountClaimOutputSchema: z.ZodObject<{
37
37
  id: z.ZodString;
38
+ expiresAt: z.ZodNullable<z.ZodDate>;
38
39
  createdAt: z.ZodDate;
39
40
  updatedAt: z.ZodNullable<z.ZodDate>;
40
- expiresAt: z.ZodNullable<z.ZodDate>;
41
41
  status: z.ZodString;
42
42
  claimUserId: z.ZodNullable<z.ZodString>;
43
43
  claimedAt: z.ZodNullable<z.ZodDate>;
@@ -57,10 +57,10 @@ declare const accountClaimMagicLinkSchema: z.ZodObject<{
57
57
  }, z.core.$strip>;
58
58
  declare const accountClaimMagicLinkOutputSchema: z.ZodObject<{
59
59
  id: z.ZodString;
60
- email: z.ZodString;
61
- createdAt: z.ZodDate;
62
- expiresAt: z.ZodNullable<z.ZodDate>;
63
60
  userId: z.ZodString;
61
+ expiresAt: z.ZodNullable<z.ZodDate>;
62
+ createdAt: z.ZodDate;
63
+ email: z.ZodString;
64
64
  claimId: z.ZodString;
65
65
  url: z.ZodString;
66
66
  }, z.core.$strip>;
@@ -15,9 +15,9 @@ declare const waitlistSchema: z.ZodObject<{
15
15
  declare const waitlistOutputSchema: z.ZodObject<{
16
16
  id: z.ZodString;
17
17
  name: z.ZodNullable<z.ZodString>;
18
- email: z.ZodNullable<z.ZodString>;
19
18
  createdAt: z.ZodDate;
20
19
  updatedAt: z.ZodNullable<z.ZodDate>;
20
+ email: z.ZodNullable<z.ZodString>;
21
21
  status: z.ZodString;
22
22
  }, z.core.$strip>;
23
23
  type WaitlistOutput = z.infer<typeof waitlistOutputSchema>;
@@ -35,9 +35,9 @@ declare const accountClaimSchema: z.ZodObject<{
35
35
  }, z.core.$strip>;
36
36
  declare const accountClaimOutputSchema: z.ZodObject<{
37
37
  id: z.ZodString;
38
+ expiresAt: z.ZodNullable<z.ZodDate>;
38
39
  createdAt: z.ZodDate;
39
40
  updatedAt: z.ZodNullable<z.ZodDate>;
40
- expiresAt: z.ZodNullable<z.ZodDate>;
41
41
  status: z.ZodString;
42
42
  claimUserId: z.ZodNullable<z.ZodString>;
43
43
  claimedAt: z.ZodNullable<z.ZodDate>;
@@ -57,10 +57,10 @@ declare const accountClaimMagicLinkSchema: z.ZodObject<{
57
57
  }, z.core.$strip>;
58
58
  declare const accountClaimMagicLinkOutputSchema: z.ZodObject<{
59
59
  id: z.ZodString;
60
- email: z.ZodString;
61
- createdAt: z.ZodDate;
62
- expiresAt: z.ZodNullable<z.ZodDate>;
63
60
  userId: z.ZodString;
61
+ expiresAt: z.ZodNullable<z.ZodDate>;
62
+ createdAt: z.ZodDate;
63
+ email: z.ZodString;
64
64
  claimId: z.ZodString;
65
65
  url: z.ZodString;
66
66
  }, z.core.$strip>;
@@ -60,9 +60,9 @@ declare const createAuthTRPCRouter: (trpcMethods: TRPCMethods, authService: Auth
60
60
  input: void;
61
61
  output: {
62
62
  id: string;
63
+ expiresAt: Date | null;
63
64
  createdAt: Date;
64
65
  updatedAt: Date | null;
65
- expiresAt: Date | null;
66
66
  status: string;
67
67
  claimUserId: string | null;
68
68
  claimedAt: Date | null;
@@ -77,10 +77,10 @@ declare const createAuthTRPCRouter: (trpcMethods: TRPCMethods, authService: Auth
77
77
  };
78
78
  output: {
79
79
  id: string;
80
- email: string;
81
- createdAt: Date;
82
- expiresAt: Date | null;
83
80
  userId: string;
81
+ expiresAt: Date | null;
82
+ createdAt: Date;
83
+ email: string;
84
84
  claimId: string;
85
85
  url: string;
86
86
  };
@@ -92,10 +92,10 @@ declare const createAuthTRPCRouter: (trpcMethods: TRPCMethods, authService: Auth
92
92
  };
93
93
  output: {
94
94
  id: string;
95
- email: string;
96
- createdAt: Date;
97
- expiresAt: Date | null;
98
95
  userId: string;
96
+ expiresAt: Date | null;
97
+ createdAt: Date;
98
+ email: string;
99
99
  claimId: string;
100
100
  url: string;
101
101
  }[];
@@ -151,9 +151,9 @@ declare const createAuthTRPCRouter: (trpcMethods: TRPCMethods, authService: Auth
151
151
  output: {
152
152
  id: string;
153
153
  name: string | null;
154
- email: string | null;
155
154
  createdAt: Date;
156
155
  updatedAt: Date | null;
156
+ email: string | null;
157
157
  status: string;
158
158
  }[];
159
159
  meta: any;
@@ -165,9 +165,9 @@ declare const createAuthTRPCRouter: (trpcMethods: TRPCMethods, authService: Auth
165
165
  output: {
166
166
  id: string;
167
167
  name: string | null;
168
- email: string | null;
169
168
  createdAt: Date;
170
169
  updatedAt: Date | null;
170
+ email: string | null;
171
171
  status: string;
172
172
  };
173
173
  meta: any;
@@ -196,9 +196,9 @@ declare const createAuthTRPCRouter: (trpcMethods: TRPCMethods, authService: Auth
196
196
  output: {
197
197
  id: string;
198
198
  name: string | null;
199
- email: string | null;
200
199
  createdAt: Date;
201
200
  updatedAt: Date | null;
201
+ email: string | null;
202
202
  status: string;
203
203
  };
204
204
  meta: any;
@@ -210,9 +210,9 @@ declare const createAuthTRPCRouter: (trpcMethods: TRPCMethods, authService: Auth
210
210
  output: {
211
211
  id: string;
212
212
  name: string | null;
213
- email: string | null;
214
213
  createdAt: Date;
215
214
  updatedAt: Date | null;
215
+ email: string | null;
216
216
  status: string;
217
217
  };
218
218
  meta: any;
@@ -224,9 +224,9 @@ declare const createAuthTRPCRouter: (trpcMethods: TRPCMethods, authService: Auth
224
224
  output: {
225
225
  id: string;
226
226
  name: string | null;
227
- email: string | null;
228
227
  createdAt: Date;
229
228
  updatedAt: Date | null;
229
+ email: string | null;
230
230
  status: string;
231
231
  };
232
232
  meta: any;
@@ -60,9 +60,9 @@ declare const createAuthTRPCRouter: (trpcMethods: TRPCMethods, authService: Auth
60
60
  input: void;
61
61
  output: {
62
62
  id: string;
63
+ expiresAt: Date | null;
63
64
  createdAt: Date;
64
65
  updatedAt: Date | null;
65
- expiresAt: Date | null;
66
66
  status: string;
67
67
  claimUserId: string | null;
68
68
  claimedAt: Date | null;
@@ -77,10 +77,10 @@ declare const createAuthTRPCRouter: (trpcMethods: TRPCMethods, authService: Auth
77
77
  };
78
78
  output: {
79
79
  id: string;
80
- email: string;
81
- createdAt: Date;
82
- expiresAt: Date | null;
83
80
  userId: string;
81
+ expiresAt: Date | null;
82
+ createdAt: Date;
83
+ email: string;
84
84
  claimId: string;
85
85
  url: string;
86
86
  };
@@ -92,10 +92,10 @@ declare const createAuthTRPCRouter: (trpcMethods: TRPCMethods, authService: Auth
92
92
  };
93
93
  output: {
94
94
  id: string;
95
- email: string;
96
- createdAt: Date;
97
- expiresAt: Date | null;
98
95
  userId: string;
96
+ expiresAt: Date | null;
97
+ createdAt: Date;
98
+ email: string;
99
99
  claimId: string;
100
100
  url: string;
101
101
  }[];
@@ -151,9 +151,9 @@ declare const createAuthTRPCRouter: (trpcMethods: TRPCMethods, authService: Auth
151
151
  output: {
152
152
  id: string;
153
153
  name: string | null;
154
- email: string | null;
155
154
  createdAt: Date;
156
155
  updatedAt: Date | null;
156
+ email: string | null;
157
157
  status: string;
158
158
  }[];
159
159
  meta: any;
@@ -165,9 +165,9 @@ declare const createAuthTRPCRouter: (trpcMethods: TRPCMethods, authService: Auth
165
165
  output: {
166
166
  id: string;
167
167
  name: string | null;
168
- email: string | null;
169
168
  createdAt: Date;
170
169
  updatedAt: Date | null;
170
+ email: string | null;
171
171
  status: string;
172
172
  };
173
173
  meta: any;
@@ -196,9 +196,9 @@ declare const createAuthTRPCRouter: (trpcMethods: TRPCMethods, authService: Auth
196
196
  output: {
197
197
  id: string;
198
198
  name: string | null;
199
- email: string | null;
200
199
  createdAt: Date;
201
200
  updatedAt: Date | null;
201
+ email: string | null;
202
202
  status: string;
203
203
  };
204
204
  meta: any;
@@ -210,9 +210,9 @@ declare const createAuthTRPCRouter: (trpcMethods: TRPCMethods, authService: Auth
210
210
  output: {
211
211
  id: string;
212
212
  name: string | null;
213
- email: string | null;
214
213
  createdAt: Date;
215
214
  updatedAt: Date | null;
215
+ email: string | null;
216
216
  status: string;
217
217
  };
218
218
  meta: any;
@@ -224,9 +224,9 @@ declare const createAuthTRPCRouter: (trpcMethods: TRPCMethods, authService: Auth
224
224
  output: {
225
225
  id: string;
226
226
  name: string | null;
227
- email: string | null;
228
227
  createdAt: Date;
229
228
  updatedAt: Date | null;
229
+ email: string | null;
230
230
  status: string;
231
231
  };
232
232
  meta: any;
@@ -5,8 +5,8 @@ let _heroui_react = require("@heroui/react");
5
5
  let react_jsx_runtime = require("react/jsx-runtime");
6
6
  let react_i18next = require("react-i18next");
7
7
  let sonner = require("sonner");
8
- let _m5kdev_frontend_modules_auth_auth_lib = require("@m5kdev/frontend/modules/auth/auth.lib");
9
8
  let react_hook_form = require("react-hook-form");
9
+ let _m5kdev_frontend_modules_auth_auth_lib = require("@m5kdev/frontend/modules/auth/auth.lib");
10
10
  //#region src/modules/auth/components/ForgotPasswordForm.tsx
11
11
  function ForgotPasswordForm() {
12
12
  const { t } = (0, react_i18next.useTranslation)();
@@ -3,8 +3,8 @@ import { Button, Input } from "@heroui/react";
3
3
  import { jsx, jsxs } from "react/jsx-runtime";
4
4
  import { useTranslation } from "react-i18next";
5
5
  import { toast } from "sonner";
6
- import { authClient } from "@m5kdev/frontend/modules/auth/auth.lib";
7
6
  import { useForm } from "react-hook-form";
7
+ import { authClient } from "@m5kdev/frontend/modules/auth/auth.lib";
8
8
  //#region src/modules/auth/components/ForgotPasswordForm.tsx
9
9
  function ForgotPasswordForm() {
10
10
  const { t } = useTranslation();
@@ -7,9 +7,9 @@ let react_jsx_runtime = require("react/jsx-runtime");
7
7
  let react_i18next = require("react-i18next");
8
8
  let react_router = require("react-router");
9
9
  let sonner = require("sonner");
10
+ let react_hook_form = require("react-hook-form");
10
11
  let _m5kdev_frontend_modules_auth_auth_lib = require("@m5kdev/frontend/modules/auth/auth.lib");
11
12
  let _m5kdev_frontend_modules_auth_hooks_useSession = require("@m5kdev/frontend/modules/auth/hooks/useSession");
12
- let react_hook_form = require("react-hook-form");
13
13
  //#region src/modules/auth/components/LoginForm.tsx
14
14
  function LoginForm({ providers }) {
15
15
  const lastMethod = _m5kdev_frontend_modules_auth_auth_lib.authClient.getLastUsedLoginMethod();
@@ -5,9 +5,9 @@ import { jsx, jsxs } from "react/jsx-runtime";
5
5
  import { useTranslation } from "react-i18next";
6
6
  import { Link as Link$1, useNavigate } from "react-router";
7
7
  import { toast } from "sonner";
8
+ import { useForm } from "react-hook-form";
8
9
  import { authClient } from "@m5kdev/frontend/modules/auth/auth.lib";
9
10
  import { useSession } from "@m5kdev/frontend/modules/auth/hooks/useSession";
10
- import { useForm } from "react-hook-form";
11
11
  //#region src/modules/auth/components/LoginForm.tsx
12
12
  function LoginForm({ providers }) {
13
13
  const lastMethod = authClient.getLastUsedLoginMethod();
@@ -6,9 +6,9 @@ let _heroui_react = require("@heroui/react");
6
6
  let react_jsx_runtime = require("react/jsx-runtime");
7
7
  let react_i18next = require("react-i18next");
8
8
  let sonner = require("sonner");
9
+ let react_hook_form = require("react-hook-form");
9
10
  let _m5kdev_frontend_modules_auth_auth_lib = require("@m5kdev/frontend/modules/auth/auth.lib");
10
11
  let _m5kdev_frontend_modules_auth_hooks_useSession = require("@m5kdev/frontend/modules/auth/hooks/useSession");
11
- let react_hook_form = require("react-hook-form");
12
12
  let zod = require("zod");
13
13
  //#region src/modules/auth/components/ProfileRoute.tsx
14
14
  zod.z.object({
@@ -4,9 +4,9 @@ import { Button, Card, CardBody, CardHeader, Input } from "@heroui/react";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
5
  import { useTranslation } from "react-i18next";
6
6
  import { toast } from "sonner";
7
+ import { useForm } from "react-hook-form";
7
8
  import { authClient } from "@m5kdev/frontend/modules/auth/auth.lib";
8
9
  import { useSession } from "@m5kdev/frontend/modules/auth/hooks/useSession";
9
- import { useForm } from "react-hook-form";
10
10
  import { z } from "zod";
11
11
  //#region src/modules/auth/components/ProfileRoute.tsx
12
12
  z.object({
@@ -5,8 +5,8 @@ let react_jsx_runtime = require("react/jsx-runtime");
5
5
  let react_i18next = require("react-i18next");
6
6
  let react_router = require("react-router");
7
7
  let sonner = require("sonner");
8
- let _m5kdev_frontend_modules_auth_auth_lib = require("@m5kdev/frontend/modules/auth/auth.lib");
9
8
  let react_hook_form = require("react-hook-form");
9
+ let _m5kdev_frontend_modules_auth_auth_lib = require("@m5kdev/frontend/modules/auth/auth.lib");
10
10
  //#region src/modules/auth/components/ResetPasswordForm.tsx
11
11
  function ResetPasswordForm() {
12
12
  const { t } = (0, react_i18next.useTranslation)();
@@ -3,8 +3,8 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useTranslation } from "react-i18next";
4
4
  import { Link as Link$1, useSearchParams } from "react-router";
5
5
  import { toast } from "sonner";
6
- import { authClient } from "@m5kdev/frontend/modules/auth/auth.lib";
7
6
  import { useForm } from "react-hook-form";
7
+ import { authClient } from "@m5kdev/frontend/modules/auth/auth.lib";
8
8
  //#region src/modules/auth/components/ResetPasswordForm.tsx
9
9
  function ResetPasswordForm() {
10
10
  const { t } = useTranslation();
@@ -6,9 +6,9 @@ let react_jsx_runtime = require("react/jsx-runtime");
6
6
  let react_i18next = require("react-i18next");
7
7
  let react_router = require("react-router");
8
8
  let sonner = require("sonner");
9
+ let react_hook_form = require("react-hook-form");
9
10
  let _m5kdev_frontend_modules_auth_auth_lib = require("@m5kdev/frontend/modules/auth/auth.lib");
10
11
  let _m5kdev_frontend_modules_auth_hooks_useSession = require("@m5kdev/frontend/modules/auth/hooks/useSession");
11
- let react_hook_form = require("react-hook-form");
12
12
  //#region src/modules/auth/components/SignupFormRoute.tsx
13
13
  function getEmailProviderUrl(email) {
14
14
  const domain = email.split("@")[1]?.toLowerCase();
@@ -4,9 +4,9 @@ import { jsx, jsxs } from "react/jsx-runtime";
4
4
  import { useTranslation } from "react-i18next";
5
5
  import { useNavigate } from "react-router";
6
6
  import { toast } from "sonner";
7
+ import { useForm } from "react-hook-form";
7
8
  import { authClient } from "@m5kdev/frontend/modules/auth/auth.lib";
8
9
  import { useSession } from "@m5kdev/frontend/modules/auth/hooks/useSession";
9
- import { useForm } from "react-hook-form";
10
10
  //#region src/modules/auth/components/SignupFormRoute.tsx
11
11
  function getEmailProviderUrl(email) {
12
12
  const domain = email.split("@")[1]?.toLowerCase();
@@ -1,8 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  require("../../../../_virtual/_rolldown/runtime.js");
3
3
  const require_src_components_ui_button = require("../../../components/ui/button.js");
4
- const require_src_modules_table_components_ColumnOrderAndVisibility = require("./ColumnOrderAndVisibility.js");
5
4
  const require_src_components_ui_table = require("../../../components/ui/table.js");
5
+ const require_src_modules_table_components_ColumnOrderAndVisibility = require("./ColumnOrderAndVisibility.js");
6
6
  const require_src_modules_table_components_TableFiltering = require("./TableFiltering.js");
7
7
  const require_src_modules_table_components_TableGroupBy = require("./TableGroupBy.js");
8
8
  const require_src_modules_table_components_TablePagination = require("./TablePagination.js");
@@ -1,6 +1,6 @@
1
1
  import { Button as Button$1 } from "../../../components/ui/button.mjs";
2
- import { ColumnOrderAndVisibility } from "./ColumnOrderAndVisibility.mjs";
3
2
  import { Table as Table$1, TableBody as TableBody$1, TableCell as TableCell$1, TableHead, TableHeader as TableHeader$1, TableRow as TableRow$1 } from "../../../components/ui/table.mjs";
3
+ import { ColumnOrderAndVisibility } from "./ColumnOrderAndVisibility.mjs";
4
4
  import { TableFiltering } from "./TableFiltering.mjs";
5
5
  import { TableGroupBy } from "./TableGroupBy.mjs";
6
6
  import { TablePagination } from "./TablePagination.mjs";
@@ -158,6 +158,15 @@ const defaultFilterMethods = {
158
158
  value: "equals",
159
159
  label: "Equals",
160
160
  component: "select"
161
+ }],
162
+ jsonArray: [{
163
+ value: "oneOf",
164
+ label: "One Of",
165
+ component: "multiSelect"
166
+ }, {
167
+ value: "equals",
168
+ label: "Equals",
169
+ component: "select"
161
170
  }]
162
171
  };
163
172
  const SINGLE_FILTER_KEY = "single-filter";
@@ -166,7 +175,8 @@ const mergeFilterMethods = (overrides) => ({
166
175
  number: overrides?.number && overrides.number.length > 0 ? overrides.number : defaultFilterMethods.number,
167
176
  date: overrides?.date && overrides.date.length > 0 ? overrides.date : defaultFilterMethods.date,
168
177
  boolean: overrides?.boolean && overrides.boolean.length > 0 ? overrides.boolean : defaultFilterMethods.boolean,
169
- enum: overrides?.enum && overrides.enum.length > 0 ? overrides.enum : defaultFilterMethods.enum
178
+ enum: overrides?.enum && overrides.enum.length > 0 ? overrides.enum : defaultFilterMethods.enum,
179
+ jsonArray: overrides?.jsonArray && overrides.jsonArray.length > 0 ? overrides.jsonArray : defaultFilterMethods.jsonArray
170
180
  });
171
181
  const TableFiltering = ({ columns, onFiltersChange, filters: initialFilters = [], onClose, singleFilter = false, filterMethods }) => {
172
182
  const [filters, setFilters] = (0, react.useState)({});
@@ -1 +1 @@
1
- {"version":3,"file":"TableFiltering.js","names":["Input","DatePicker","DateRangePicker","Select","SelectItem","transformFiltersToHeroUI","transformFiltersFromHeroUI","Button","PlusIcon","XIcon"],"sources":["../../../../../src/modules/table/components/TableFiltering.tsx"],"sourcesContent":["import {\r\n DatePicker,\r\n DateRangePicker,\r\n type DateValue,\r\n Input,\r\n Select,\r\n SelectItem,\r\n type SharedSelection,\r\n} from \"@heroui/react\";\r\nimport { getLocalTimeZone, today } from \"@internationalized/date\";\r\nimport type { QueryFilters } from \"@m5kdev/commons/modules/schemas/query.schema\";\r\nimport type {\r\n ColumnDataType,\r\n ComponentForFilterMethod,\r\n FilterMethod,\r\n FilterMethods,\r\n} from \"@m5kdev/commons/modules/table/filter.types\";\r\nimport { PlusIcon, XIcon } from \"lucide-react\";\r\nimport type { ReactNode } from \"react\";\r\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\r\nimport { Button } from \"../../../components/ui/button\";\r\nimport {\r\n type FilterValue,\r\n type HeroUIFilter,\r\n transformFiltersFromHeroUI,\r\n transformFiltersToHeroUI,\r\n} from \"../filterTransformers\";\r\n\r\ntype ComponentRenderer = (\r\n value: FilterValue,\r\n onChange: (value: FilterValue) => void,\r\n options?: { label: string; value: string }[]\r\n) => ReactNode;\r\n\r\nconst componentForFilterMethod: Record<ComponentForFilterMethod, ComponentRenderer> = {\r\n text: (value, onChange) => (\r\n <Input\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n value={(value as string) ?? \"\"}\r\n onChange={(e) => onChange(e.target.value)}\r\n />\r\n ),\r\n number: (value, onChange) => (\r\n <Input\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n type=\"number\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n value={(value as number | null)?.toString() ?? \"\"}\r\n onChange={(e) => onChange(Number.parseFloat(e.target.value) || 0)}\r\n />\r\n ),\r\n date: (value, onChange) => (\r\n <DatePicker\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n value={(value as any) ?? undefined}\r\n onChange={(date) => date && onChange(date as FilterValue)}\r\n maxValue={today(getLocalTimeZone()) as unknown as DateValue}\r\n />\r\n ),\r\n range: (value, onChange) => (\r\n <DateRangePicker\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n value={(value as any) ?? undefined}\r\n onChange={(range) => range && onChange(range as FilterValue)}\r\n maxValue={today(getLocalTimeZone()) as unknown as DateValue}\r\n />\r\n ),\r\n radio: (value, onChange) => (\r\n <Select\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n selectedKeys={(value as boolean | null) ? [\"true\"] : [\"false\"]}\r\n onSelectionChange={(keys) => onChange(keys.currentKey === \"true\")}\r\n >\r\n <SelectItem key=\"true\" className=\"text-sm\">\r\n True\r\n </SelectItem>\r\n <SelectItem key=\"false\" className=\"text-sm\">\r\n False\r\n </SelectItem>\r\n </Select>\r\n ),\r\n select: (value, onChange, options = []) => (\r\n <Select\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n selectedKeys={(value as SharedSelection) ?? new Set()}\r\n onSelectionChange={(keys) => keys && onChange(keys as FilterValue)}\r\n >\r\n {options.map((option) => (\r\n <SelectItem key={option.value} className=\"text-sm\">\r\n {option.label}\r\n </SelectItem>\r\n ))}\r\n </Select>\r\n ),\r\n multiSelect: (value, onChange, options = []) => (\r\n <Select\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n selectionMode=\"multiple\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n selectedKeys={value ? new Set(value as SharedSelection) : new Set()}\r\n onSelectionChange={(keys) => onChange(keys as FilterValue)}\r\n >\r\n {options.map((option) => (\r\n <SelectItem key={option.value} className=\"text-sm\">\r\n {option.label}\r\n </SelectItem>\r\n ))}\r\n </Select>\r\n ),\r\n};\r\n\r\nconst defaultFilterMethods: FilterMethods = {\r\n string: [\r\n { value: \"contains\", label: \"Contains\", component: \"text\" },\r\n { value: \"equals\", label: \"Equals\", component: \"text\" },\r\n { value: \"starts_with\", label: \"Starts With\", component: \"text\" },\r\n { value: \"ends_with\", label: \"Ends With\", component: \"text\" },\r\n ],\r\n number: [\r\n { value: \"equals\", label: \"Equals\", component: \"number\" },\r\n { value: \"greater_than\", label: \"Greater Than\", component: \"number\" },\r\n { value: \"less_than\", label: \"Less Than\", component: \"number\" },\r\n ],\r\n date: [\r\n { value: \"on\", label: \"On\", component: \"date\" },\r\n { value: \"between\", label: \"Between\", component: \"range\" },\r\n { value: \"before\", label: \"Before\", component: \"date\" },\r\n { value: \"after\", label: \"After\", component: \"date\" },\r\n { value: \"intersect\", label: \"During\", component: \"range\" },\r\n ],\r\n boolean: [{ value: \"equals\", label: \"Equals\", component: \"radio\" }],\r\n enum: [\r\n { value: \"oneOf\", label: \"One Of\", component: \"multiSelect\" },\r\n { value: \"equals\", label: \"Equals\", component: \"select\" },\r\n ],\r\n};\r\n\r\nconst SINGLE_FILTER_KEY = \"single-filter\";\r\n\r\ntype TableFilteringProps = {\r\n columns: {\r\n id: string;\r\n label: string;\r\n type?: ColumnDataType | null;\r\n options?: { label: string; value: string }[] | null;\r\n endColumnId?: string | null;\r\n periodStartColumnId?: string | null;\r\n periodEndColumnId?: string | null;\r\n }[];\r\n onFiltersChange: (filters: QueryFilters) => void;\r\n filters: QueryFilters;\r\n onClose?: () => void;\r\n singleFilter?: boolean;\r\n filterMethods?: Partial<FilterMethods>;\r\n};\r\n\r\nconst mergeFilterMethods = (overrides?: Partial<FilterMethods>): FilterMethods => ({\r\n string:\r\n overrides?.string && overrides.string.length > 0\r\n ? overrides.string\r\n : defaultFilterMethods.string,\r\n number:\r\n overrides?.number && overrides.number.length > 0\r\n ? overrides.number\r\n : defaultFilterMethods.number,\r\n date: overrides?.date && overrides.date.length > 0 ? overrides.date : defaultFilterMethods.date,\r\n boolean:\r\n overrides?.boolean && overrides.boolean.length > 0\r\n ? overrides.boolean\r\n : defaultFilterMethods.boolean,\r\n enum: overrides?.enum && overrides.enum.length > 0 ? overrides.enum : defaultFilterMethods.enum,\r\n});\r\n\r\nexport const TableFiltering = ({\r\n columns,\r\n onFiltersChange,\r\n filters: initialFilters = [],\r\n onClose,\r\n singleFilter = false,\r\n filterMethods,\r\n}: TableFilteringProps) => {\r\n const [filters, setFilters] = useState<Record<string, HeroUIFilter>>({});\r\n const effectiveFilterMethods = useMemo(() => mergeFilterMethods(filterMethods), [filterMethods]);\r\n\r\n const createEmptyFilter = useCallback(\r\n (): HeroUIFilter => ({\r\n columnId: \"\",\r\n type: null,\r\n value: null,\r\n method: null,\r\n options: null,\r\n endColumnId: null,\r\n periodStartColumnId: null,\r\n periodEndColumnId: null,\r\n }),\r\n []\r\n );\r\n\r\n const addFilter = useCallback(() => {\r\n setFilters((prev) => {\r\n if (!singleFilter) {\r\n const filterId = crypto.randomUUID();\r\n return {\r\n ...prev,\r\n [filterId]: createEmptyFilter(),\r\n };\r\n }\r\n\r\n // single filter mode\r\n return Object.keys(prev).length > 0 ? prev : { [SINGLE_FILTER_KEY]: createEmptyFilter() };\r\n });\r\n }, [createEmptyFilter, singleFilter]);\r\n\r\n const removeFilter = useCallback(\r\n (filterId: string) => {\r\n setFilters((prev) => {\r\n if (!singleFilter) {\r\n const { [filterId]: _, ...rest } = prev;\r\n return rest;\r\n }\r\n\r\n // single filter mode resets the lone filter\r\n return { [SINGLE_FILTER_KEY]: createEmptyFilter() };\r\n });\r\n },\r\n [createEmptyFilter, singleFilter]\r\n );\r\n\r\n const columnsMap = useMemo(\r\n () => new Map(columns.map((column) => [column.id, column])),\r\n [columns]\r\n );\r\n\r\n useEffect(() => {\r\n // Transform initialFilters from FiltersToApply format to HeroUIFilter format\r\n const transformedFilters = transformFiltersToHeroUI(\r\n initialFilters,\r\n columnsMap,\r\n effectiveFilterMethods\r\n );\r\n\r\n if (!singleFilter) {\r\n setFilters(Object.fromEntries(transformedFilters.map((filter) => [filter.columnId, filter])));\r\n return;\r\n }\r\n\r\n const firstFilter = transformedFilters[0];\r\n setFilters({\r\n [SINGLE_FILTER_KEY]: firstFilter ?? createEmptyFilter(),\r\n });\r\n }, [createEmptyFilter, initialFilters, singleFilter, columnsMap, effectiveFilterMethods]);\r\n\r\n const selectColumn = useCallback(\r\n (filterId: string, columnId: string) => {\r\n setFilters((prev) => {\r\n const oldFilter = prev[filterId];\r\n if (!oldFilter) return prev;\r\n const column = columnsMap.get(columnId);\r\n if (!column) return prev;\r\n\r\n // If Period column, auto-set intersect method\r\n const isPeriodColumn = columnId.endsWith(\"__period\");\r\n const intersectMethod = isPeriodColumn\r\n ? (effectiveFilterMethods.date.find((m) => m.value === \"intersect\") ?? null)\r\n : null;\r\n\r\n return {\r\n ...prev,\r\n [filterId]: {\r\n ...oldFilter,\r\n columnId,\r\n type: column.type ?? null,\r\n options: column.options ?? null,\r\n endColumnId: column.endColumnId ?? null,\r\n periodStartColumnId: column.periodStartColumnId ?? null,\r\n periodEndColumnId: column.periodEndColumnId ?? null,\r\n method: intersectMethod,\r\n value: null,\r\n },\r\n };\r\n });\r\n },\r\n [columnsMap, effectiveFilterMethods]\r\n );\r\n\r\n const selectMethod = useCallback((filterId: string, method: FilterMethod) => {\r\n setFilters((prev) => {\r\n const oldFilter = prev[filterId];\r\n if (!oldFilter) return prev;\r\n return {\r\n ...prev,\r\n [filterId]: { ...oldFilter, method, value: null },\r\n };\r\n });\r\n }, []);\r\n\r\n const selectValue = useCallback((filterId: string, value: FilterValue) => {\r\n setFilters((prev) => {\r\n const oldFilter = prev[filterId];\r\n if (!oldFilter) return prev;\r\n return {\r\n ...prev,\r\n [filterId]: { ...oldFilter, value },\r\n };\r\n });\r\n }, []);\r\n\r\n const filterEntries = useMemo(() => Object.entries(filters), [filters]);\r\n\r\n const availableColumnsMap = useMemo(() => {\r\n const map = new Map<string, typeof columns>();\r\n filterEntries.forEach(([filterId]) => {\r\n const columnsUsedByOtherFilters = new Set(\r\n filterEntries\r\n .filter(([id]) => id !== filterId)\r\n .map(([_, f]) => f.columnId)\r\n .filter((id) => id !== \"\")\r\n );\r\n map.set(\r\n filterId,\r\n columns.filter((column) => !columnsUsedByOtherFilters.has(column.id))\r\n );\r\n });\r\n return map;\r\n }, [filterEntries, columns]);\r\n\r\n const applyFilters = useCallback(() => {\r\n const heroUIFilters = Object.values(filters);\r\n const filtersToApply = transformFiltersFromHeroUI(heroUIFilters);\r\n onFiltersChange(filtersToApply);\r\n onClose?.();\r\n }, [filters, onFiltersChange, onClose]);\r\n\r\n return (\r\n <div className=\"flex flex-col gap-2 p-1 min-w-[600px]\">\r\n {filterEntries.map(([filterId, filter]) => {\r\n const availableColumns = availableColumnsMap.get(filterId) ?? columns;\r\n return (\r\n <TableFilteringItem\r\n key={filterId}\r\n id={filterId}\r\n filter={filter}\r\n columns={availableColumns}\r\n selectColumn={selectColumn}\r\n selectMethod={selectMethod}\r\n removeFilter={removeFilter}\r\n selectValue={selectValue}\r\n filterMethods={effectiveFilterMethods}\r\n />\r\n );\r\n })}\r\n {!singleFilter && (\r\n <Button variant=\"outline\" size=\"sm\" onClick={addFilter}>\r\n <PlusIcon className=\"h-4 w-4\" />\r\n Add Filter\r\n </Button>\r\n )}\r\n <Button onClick={applyFilters}>{singleFilter ? \"Apply Filter\" : \"Apply Filters\"}</Button>\r\n </div>\r\n );\r\n};\r\n\r\nconst TableFilteringItem = ({\r\n id,\r\n filter,\r\n columns,\r\n selectColumn,\r\n selectMethod,\r\n selectValue,\r\n removeFilter,\r\n filterMethods,\r\n}: {\r\n id: string;\r\n filter: HeroUIFilter;\r\n columns: {\r\n id: string;\r\n label: string;\r\n type?: ColumnDataType | null;\r\n options?: { label: string; value: string }[] | null;\r\n endColumnId?: string | null;\r\n periodStartColumnId?: string | null;\r\n periodEndColumnId?: string | null;\r\n }[];\r\n selectColumn: (filterId: string, columnId: string) => void;\r\n selectMethod: (filterId: string, method: FilterMethod) => void;\r\n selectValue: (filterId: string, value: FilterValue) => void;\r\n removeFilter: (filterId: string) => void;\r\n filterMethods: FilterMethods;\r\n}) => {\r\n const handleColumnChange = useCallback(\r\n (keys: any) => {\r\n const columnId = String(keys.currentKey);\r\n selectColumn(id, columnId);\r\n },\r\n [id, selectColumn]\r\n );\r\n\r\n const methodsForType = useMemo(() => {\r\n if (!filter.type) return [];\r\n\r\n // Period columns only support intersect method\r\n const isPeriodColumn = filter.columnId.endsWith(\"__period\");\r\n if (isPeriodColumn) {\r\n const intersectMethod = filterMethods.date.find((m) => m.value === \"intersect\");\r\n return intersectMethod ? [intersectMethod] : [];\r\n }\r\n\r\n const baseMethods = filterMethods[filter.type as ColumnDataType] ?? [];\r\n const emptyMethods: FilterMethod[] = [\r\n { value: \"isEmpty\", label: \"Is Empty\", component: null },\r\n { value: \"isNotEmpty\", label: \"Is Not Empty\", component: null },\r\n ];\r\n\r\n if (filter.type !== \"boolean\" && filter.type !== \"date\") {\r\n return [...baseMethods, ...emptyMethods];\r\n }\r\n\r\n return baseMethods;\r\n }, [filter.type, filter.columnId, filter.method?.value, filterMethods]);\r\n\r\n const handleMethodChange = useCallback(\r\n (keys: any) => {\r\n if (!filter.type) return;\r\n // Use methodsForType instead of filterMethods to include dynamically added methods\r\n const method = methodsForType.find(\r\n (currentMethod: FilterMethod) => currentMethod.value === String(keys.currentKey)\r\n );\r\n if (method) {\r\n selectMethod(id, method);\r\n }\r\n },\r\n [id, filter.type, selectMethod, methodsForType]\r\n );\r\n\r\n const handleValueChange = useCallback(\r\n (value: FilterValue) => {\r\n selectValue(id, value);\r\n },\r\n [id, selectValue]\r\n );\r\n\r\n const methodSelect = useMemo(() => {\r\n if (!filter.type) return null;\r\n return (\r\n <Select\r\n size=\"sm\"\r\n aria-label=\"Select Method\"\r\n className=\"w-40 flex-shrink-0 text-sm\"\r\n selectedKeys={filter.method?.value ? [filter.method.value] : []}\r\n onSelectionChange={handleMethodChange}\r\n popoverProps={{\r\n className: \"w-auto min-w-max\",\r\n }}\r\n >\r\n {methodsForType.map((method: FilterMethod) => (\r\n <SelectItem key={method.value} className=\"text-sm\">\r\n {method.label}\r\n </SelectItem>\r\n ))}\r\n </Select>\r\n );\r\n }, [filter.type, filter.method?.value, methodsForType, handleMethodChange]);\r\n\r\n const filterValueComponent = useMemo(() => {\r\n if (!filter.method?.component) {\r\n return <div className=\"flex-1 min-w-0\" />;\r\n }\r\n const component = filter.method.component as ComponentForFilterMethod;\r\n const ComponentFn =\r\n componentForFilterMethod[component as keyof typeof componentForFilterMethod];\r\n if (!ComponentFn) return <div className=\"flex-1 min-w-0\" />;\r\n return ComponentFn(filter.value as any, handleValueChange, filter.options ?? []);\r\n }, [filter.method?.component, filter.value, filter.options, handleValueChange]);\r\n\r\n const columnSelectItems = useMemo(\r\n () =>\r\n columns.map((column) => (\r\n <SelectItem key={column.id} className=\"text-sm\">\r\n {String(column.label)}\r\n </SelectItem>\r\n )),\r\n [columns]\r\n );\r\n\r\n return (\r\n <div className=\"flex items-center gap-2 w-full\">\r\n <div className=\"flex flex-1 items-center gap-2 min-w-0\">\r\n <Select\r\n size=\"sm\"\r\n aria-label=\"Select Column\"\r\n className=\"w-40 flex-shrink-0 text-sm\"\r\n selectedKeys={filter.columnId ? [filter.columnId] : []}\r\n onSelectionChange={handleColumnChange}\r\n popoverProps={{\r\n className: \"w-auto min-w-max\",\r\n }}\r\n >\r\n {columnSelectItems}\r\n </Select>\r\n {methodSelect}\r\n {filterValueComponent}\r\n </div>\r\n <Button variant=\"outline\" size=\"sm\" onClick={() => removeFilter(id)}>\r\n <XIcon className=\"h-4 w-4\" />\r\n </Button>\r\n </div>\r\n );\r\n};\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"TableFiltering.js","names":["Input","DatePicker","DateRangePicker","Select","SelectItem","transformFiltersToHeroUI","transformFiltersFromHeroUI","Button","PlusIcon","XIcon"],"sources":["../../../../../src/modules/table/components/TableFiltering.tsx"],"sourcesContent":["import {\r\n DatePicker,\r\n DateRangePicker,\r\n type DateValue,\r\n Input,\r\n Select,\r\n SelectItem,\r\n type SharedSelection,\r\n} from \"@heroui/react\";\r\nimport { getLocalTimeZone, today } from \"@internationalized/date\";\r\nimport type { QueryFilters } from \"@m5kdev/commons/modules/schemas/query.schema\";\r\nimport type {\r\n ColumnDataType,\r\n ComponentForFilterMethod,\r\n FilterMethod,\r\n FilterMethods,\r\n} from \"@m5kdev/commons/modules/table/filter.types\";\r\nimport { PlusIcon, XIcon } from \"lucide-react\";\r\nimport type { ReactNode } from \"react\";\r\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\r\nimport { Button } from \"../../../components/ui/button\";\r\nimport {\r\n type FilterValue,\r\n type HeroUIFilter,\r\n transformFiltersFromHeroUI,\r\n transformFiltersToHeroUI,\r\n} from \"../filterTransformers\";\r\n\r\ntype ComponentRenderer = (\r\n value: FilterValue,\r\n onChange: (value: FilterValue) => void,\r\n options?: { label: string; value: string }[]\r\n) => ReactNode;\r\n\r\nconst componentForFilterMethod: Record<ComponentForFilterMethod, ComponentRenderer> = {\r\n text: (value, onChange) => (\r\n <Input\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n value={(value as string) ?? \"\"}\r\n onChange={(e) => onChange(e.target.value)}\r\n />\r\n ),\r\n number: (value, onChange) => (\r\n <Input\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n type=\"number\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n value={(value as number | null)?.toString() ?? \"\"}\r\n onChange={(e) => onChange(Number.parseFloat(e.target.value) || 0)}\r\n />\r\n ),\r\n date: (value, onChange) => (\r\n <DatePicker\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n value={(value as any) ?? undefined}\r\n onChange={(date) => date && onChange(date as FilterValue)}\r\n maxValue={today(getLocalTimeZone()) as unknown as DateValue}\r\n />\r\n ),\r\n range: (value, onChange) => (\r\n <DateRangePicker\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n value={(value as any) ?? undefined}\r\n onChange={(range) => range && onChange(range as FilterValue)}\r\n maxValue={today(getLocalTimeZone()) as unknown as DateValue}\r\n />\r\n ),\r\n radio: (value, onChange) => (\r\n <Select\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n selectedKeys={(value as boolean | null) ? [\"true\"] : [\"false\"]}\r\n onSelectionChange={(keys) => onChange(keys.currentKey === \"true\")}\r\n >\r\n <SelectItem key=\"true\" className=\"text-sm\">\r\n True\r\n </SelectItem>\r\n <SelectItem key=\"false\" className=\"text-sm\">\r\n False\r\n </SelectItem>\r\n </Select>\r\n ),\r\n select: (value, onChange, options = []) => (\r\n <Select\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n selectedKeys={(value as SharedSelection) ?? new Set()}\r\n onSelectionChange={(keys) => keys && onChange(keys as FilterValue)}\r\n >\r\n {options.map((option) => (\r\n <SelectItem key={option.value} className=\"text-sm\">\r\n {option.label}\r\n </SelectItem>\r\n ))}\r\n </Select>\r\n ),\r\n multiSelect: (value, onChange, options = []) => (\r\n <Select\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n selectionMode=\"multiple\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n selectedKeys={value ? new Set(value as SharedSelection) : new Set()}\r\n onSelectionChange={(keys) => onChange(keys as FilterValue)}\r\n >\r\n {options.map((option) => (\r\n <SelectItem key={option.value} className=\"text-sm\">\r\n {option.label}\r\n </SelectItem>\r\n ))}\r\n </Select>\r\n ),\r\n};\r\n\r\nconst defaultFilterMethods: FilterMethods = {\r\n string: [\r\n { value: \"contains\", label: \"Contains\", component: \"text\" },\r\n { value: \"equals\", label: \"Equals\", component: \"text\" },\r\n { value: \"starts_with\", label: \"Starts With\", component: \"text\" },\r\n { value: \"ends_with\", label: \"Ends With\", component: \"text\" },\r\n ],\r\n number: [\r\n { value: \"equals\", label: \"Equals\", component: \"number\" },\r\n { value: \"greater_than\", label: \"Greater Than\", component: \"number\" },\r\n { value: \"less_than\", label: \"Less Than\", component: \"number\" },\r\n ],\r\n date: [\r\n { value: \"on\", label: \"On\", component: \"date\" },\r\n { value: \"between\", label: \"Between\", component: \"range\" },\r\n { value: \"before\", label: \"Before\", component: \"date\" },\r\n { value: \"after\", label: \"After\", component: \"date\" },\r\n { value: \"intersect\", label: \"During\", component: \"range\" },\r\n ],\r\n boolean: [{ value: \"equals\", label: \"Equals\", component: \"radio\" }],\r\n enum: [\r\n { value: \"oneOf\", label: \"One Of\", component: \"multiSelect\" },\r\n { value: \"equals\", label: \"Equals\", component: \"select\" },\r\n ],\r\n jsonArray: [\r\n { value: \"oneOf\", label: \"One Of\", component: \"multiSelect\" },\r\n { value: \"equals\", label: \"Equals\", component: \"select\" },\r\n ],\r\n};\r\n\r\nconst SINGLE_FILTER_KEY = \"single-filter\";\r\n\r\ntype TableFilteringProps = {\r\n columns: {\r\n id: string;\r\n label: string;\r\n type?: ColumnDataType | null;\r\n options?: { label: string; value: string }[] | null;\r\n endColumnId?: string | null;\r\n periodStartColumnId?: string | null;\r\n periodEndColumnId?: string | null;\r\n }[];\r\n onFiltersChange: (filters: QueryFilters) => void;\r\n filters: QueryFilters;\r\n onClose?: () => void;\r\n singleFilter?: boolean;\r\n filterMethods?: Partial<FilterMethods>;\r\n};\r\n\r\nconst mergeFilterMethods = (overrides?: Partial<FilterMethods>): FilterMethods => ({\r\n string:\r\n overrides?.string && overrides.string.length > 0\r\n ? overrides.string\r\n : defaultFilterMethods.string,\r\n number:\r\n overrides?.number && overrides.number.length > 0\r\n ? overrides.number\r\n : defaultFilterMethods.number,\r\n date: overrides?.date && overrides.date.length > 0 ? overrides.date : defaultFilterMethods.date,\r\n boolean:\r\n overrides?.boolean && overrides.boolean.length > 0\r\n ? overrides.boolean\r\n : defaultFilterMethods.boolean,\r\n enum: overrides?.enum && overrides.enum.length > 0 ? overrides.enum : defaultFilterMethods.enum,\r\n jsonArray:\r\n overrides?.jsonArray && overrides.jsonArray.length > 0\r\n ? overrides.jsonArray\r\n : defaultFilterMethods.jsonArray,\r\n});\r\n\r\nexport const TableFiltering = ({\r\n columns,\r\n onFiltersChange,\r\n filters: initialFilters = [],\r\n onClose,\r\n singleFilter = false,\r\n filterMethods,\r\n}: TableFilteringProps) => {\r\n const [filters, setFilters] = useState<Record<string, HeroUIFilter>>({});\r\n const effectiveFilterMethods = useMemo(() => mergeFilterMethods(filterMethods), [filterMethods]);\r\n\r\n const createEmptyFilter = useCallback(\r\n (): HeroUIFilter => ({\r\n columnId: \"\",\r\n type: null,\r\n value: null,\r\n method: null,\r\n options: null,\r\n endColumnId: null,\r\n periodStartColumnId: null,\r\n periodEndColumnId: null,\r\n }),\r\n []\r\n );\r\n\r\n const addFilter = useCallback(() => {\r\n setFilters((prev) => {\r\n if (!singleFilter) {\r\n const filterId = crypto.randomUUID();\r\n return {\r\n ...prev,\r\n [filterId]: createEmptyFilter(),\r\n };\r\n }\r\n\r\n // single filter mode\r\n return Object.keys(prev).length > 0 ? prev : { [SINGLE_FILTER_KEY]: createEmptyFilter() };\r\n });\r\n }, [createEmptyFilter, singleFilter]);\r\n\r\n const removeFilter = useCallback(\r\n (filterId: string) => {\r\n setFilters((prev) => {\r\n if (!singleFilter) {\r\n const { [filterId]: _, ...rest } = prev;\r\n return rest;\r\n }\r\n\r\n // single filter mode resets the lone filter\r\n return { [SINGLE_FILTER_KEY]: createEmptyFilter() };\r\n });\r\n },\r\n [createEmptyFilter, singleFilter]\r\n );\r\n\r\n const columnsMap = useMemo(\r\n () => new Map(columns.map((column) => [column.id, column])),\r\n [columns]\r\n );\r\n\r\n useEffect(() => {\r\n // Transform initialFilters from FiltersToApply format to HeroUIFilter format\r\n const transformedFilters = transformFiltersToHeroUI(\r\n initialFilters,\r\n columnsMap,\r\n effectiveFilterMethods\r\n );\r\n\r\n if (!singleFilter) {\r\n setFilters(Object.fromEntries(transformedFilters.map((filter) => [filter.columnId, filter])));\r\n return;\r\n }\r\n\r\n const firstFilter = transformedFilters[0];\r\n setFilters({\r\n [SINGLE_FILTER_KEY]: firstFilter ?? createEmptyFilter(),\r\n });\r\n }, [createEmptyFilter, initialFilters, singleFilter, columnsMap, effectiveFilterMethods]);\r\n\r\n const selectColumn = useCallback(\r\n (filterId: string, columnId: string) => {\r\n setFilters((prev) => {\r\n const oldFilter = prev[filterId];\r\n if (!oldFilter) return prev;\r\n const column = columnsMap.get(columnId);\r\n if (!column) return prev;\r\n\r\n // If Period column, auto-set intersect method\r\n const isPeriodColumn = columnId.endsWith(\"__period\");\r\n const intersectMethod = isPeriodColumn\r\n ? (effectiveFilterMethods.date.find((m) => m.value === \"intersect\") ?? null)\r\n : null;\r\n\r\n return {\r\n ...prev,\r\n [filterId]: {\r\n ...oldFilter,\r\n columnId,\r\n type: column.type ?? null,\r\n options: column.options ?? null,\r\n endColumnId: column.endColumnId ?? null,\r\n periodStartColumnId: column.periodStartColumnId ?? null,\r\n periodEndColumnId: column.periodEndColumnId ?? null,\r\n method: intersectMethod,\r\n value: null,\r\n },\r\n };\r\n });\r\n },\r\n [columnsMap, effectiveFilterMethods]\r\n );\r\n\r\n const selectMethod = useCallback((filterId: string, method: FilterMethod) => {\r\n setFilters((prev) => {\r\n const oldFilter = prev[filterId];\r\n if (!oldFilter) return prev;\r\n return {\r\n ...prev,\r\n [filterId]: { ...oldFilter, method, value: null },\r\n };\r\n });\r\n }, []);\r\n\r\n const selectValue = useCallback((filterId: string, value: FilterValue) => {\r\n setFilters((prev) => {\r\n const oldFilter = prev[filterId];\r\n if (!oldFilter) return prev;\r\n return {\r\n ...prev,\r\n [filterId]: { ...oldFilter, value },\r\n };\r\n });\r\n }, []);\r\n\r\n const filterEntries = useMemo(() => Object.entries(filters), [filters]);\r\n\r\n const availableColumnsMap = useMemo(() => {\r\n const map = new Map<string, typeof columns>();\r\n filterEntries.forEach(([filterId]) => {\r\n const columnsUsedByOtherFilters = new Set(\r\n filterEntries\r\n .filter(([id]) => id !== filterId)\r\n .map(([_, f]) => f.columnId)\r\n .filter((id) => id !== \"\")\r\n );\r\n map.set(\r\n filterId,\r\n columns.filter((column) => !columnsUsedByOtherFilters.has(column.id))\r\n );\r\n });\r\n return map;\r\n }, [filterEntries, columns]);\r\n\r\n const applyFilters = useCallback(() => {\r\n const heroUIFilters = Object.values(filters);\r\n const filtersToApply = transformFiltersFromHeroUI(heroUIFilters);\r\n onFiltersChange(filtersToApply);\r\n onClose?.();\r\n }, [filters, onFiltersChange, onClose]);\r\n\r\n return (\r\n <div className=\"flex flex-col gap-2 p-1 min-w-[600px]\">\r\n {filterEntries.map(([filterId, filter]) => {\r\n const availableColumns = availableColumnsMap.get(filterId) ?? columns;\r\n return (\r\n <TableFilteringItem\r\n key={filterId}\r\n id={filterId}\r\n filter={filter}\r\n columns={availableColumns}\r\n selectColumn={selectColumn}\r\n selectMethod={selectMethod}\r\n removeFilter={removeFilter}\r\n selectValue={selectValue}\r\n filterMethods={effectiveFilterMethods}\r\n />\r\n );\r\n })}\r\n {!singleFilter && (\r\n <Button variant=\"outline\" size=\"sm\" onClick={addFilter}>\r\n <PlusIcon className=\"h-4 w-4\" />\r\n Add Filter\r\n </Button>\r\n )}\r\n <Button onClick={applyFilters}>{singleFilter ? \"Apply Filter\" : \"Apply Filters\"}</Button>\r\n </div>\r\n );\r\n};\r\n\r\nconst TableFilteringItem = ({\r\n id,\r\n filter,\r\n columns,\r\n selectColumn,\r\n selectMethod,\r\n selectValue,\r\n removeFilter,\r\n filterMethods,\r\n}: {\r\n id: string;\r\n filter: HeroUIFilter;\r\n columns: {\r\n id: string;\r\n label: string;\r\n type?: ColumnDataType | null;\r\n options?: { label: string; value: string }[] | null;\r\n endColumnId?: string | null;\r\n periodStartColumnId?: string | null;\r\n periodEndColumnId?: string | null;\r\n }[];\r\n selectColumn: (filterId: string, columnId: string) => void;\r\n selectMethod: (filterId: string, method: FilterMethod) => void;\r\n selectValue: (filterId: string, value: FilterValue) => void;\r\n removeFilter: (filterId: string) => void;\r\n filterMethods: FilterMethods;\r\n}) => {\r\n const handleColumnChange = useCallback(\r\n (keys: any) => {\r\n const columnId = String(keys.currentKey);\r\n selectColumn(id, columnId);\r\n },\r\n [id, selectColumn]\r\n );\r\n\r\n const methodsForType = useMemo(() => {\r\n if (!filter.type) return [];\r\n\r\n // Period columns only support intersect method\r\n const isPeriodColumn = filter.columnId.endsWith(\"__period\");\r\n if (isPeriodColumn) {\r\n const intersectMethod = filterMethods.date.find((m) => m.value === \"intersect\");\r\n return intersectMethod ? [intersectMethod] : [];\r\n }\r\n\r\n const baseMethods = filterMethods[filter.type as ColumnDataType] ?? [];\r\n const emptyMethods: FilterMethod[] = [\r\n { value: \"isEmpty\", label: \"Is Empty\", component: null },\r\n { value: \"isNotEmpty\", label: \"Is Not Empty\", component: null },\r\n ];\r\n\r\n if (filter.type !== \"boolean\" && filter.type !== \"date\") {\r\n return [...baseMethods, ...emptyMethods];\r\n }\r\n\r\n return baseMethods;\r\n }, [filter.type, filter.columnId, filter.method?.value, filterMethods]);\r\n\r\n const handleMethodChange = useCallback(\r\n (keys: any) => {\r\n if (!filter.type) return;\r\n // Use methodsForType instead of filterMethods to include dynamically added methods\r\n const method = methodsForType.find(\r\n (currentMethod: FilterMethod) => currentMethod.value === String(keys.currentKey)\r\n );\r\n if (method) {\r\n selectMethod(id, method);\r\n }\r\n },\r\n [id, filter.type, selectMethod, methodsForType]\r\n );\r\n\r\n const handleValueChange = useCallback(\r\n (value: FilterValue) => {\r\n selectValue(id, value);\r\n },\r\n [id, selectValue]\r\n );\r\n\r\n const methodSelect = useMemo(() => {\r\n if (!filter.type) return null;\r\n return (\r\n <Select\r\n size=\"sm\"\r\n aria-label=\"Select Method\"\r\n className=\"w-40 flex-shrink-0 text-sm\"\r\n selectedKeys={filter.method?.value ? [filter.method.value] : []}\r\n onSelectionChange={handleMethodChange}\r\n popoverProps={{\r\n className: \"w-auto min-w-max\",\r\n }}\r\n >\r\n {methodsForType.map((method: FilterMethod) => (\r\n <SelectItem key={method.value} className=\"text-sm\">\r\n {method.label}\r\n </SelectItem>\r\n ))}\r\n </Select>\r\n );\r\n }, [filter.type, filter.method?.value, methodsForType, handleMethodChange]);\r\n\r\n const filterValueComponent = useMemo(() => {\r\n if (!filter.method?.component) {\r\n return <div className=\"flex-1 min-w-0\" />;\r\n }\r\n const component = filter.method.component as ComponentForFilterMethod;\r\n const ComponentFn =\r\n componentForFilterMethod[component as keyof typeof componentForFilterMethod];\r\n if (!ComponentFn) return <div className=\"flex-1 min-w-0\" />;\r\n return ComponentFn(filter.value as any, handleValueChange, filter.options ?? []);\r\n }, [filter.method?.component, filter.value, filter.options, handleValueChange]);\r\n\r\n const columnSelectItems = useMemo(\r\n () =>\r\n columns.map((column) => (\r\n <SelectItem key={column.id} className=\"text-sm\">\r\n {String(column.label)}\r\n </SelectItem>\r\n )),\r\n [columns]\r\n );\r\n\r\n return (\r\n <div className=\"flex items-center gap-2 w-full\">\r\n <div className=\"flex flex-1 items-center gap-2 min-w-0\">\r\n <Select\r\n size=\"sm\"\r\n aria-label=\"Select Column\"\r\n className=\"w-40 flex-shrink-0 text-sm\"\r\n selectedKeys={filter.columnId ? [filter.columnId] : []}\r\n onSelectionChange={handleColumnChange}\r\n popoverProps={{\r\n className: \"w-auto min-w-max\",\r\n }}\r\n >\r\n {columnSelectItems}\r\n </Select>\r\n {methodSelect}\r\n {filterValueComponent}\r\n </div>\r\n <Button variant=\"outline\" size=\"sm\" onClick={() => removeFilter(id)}>\r\n <XIcon className=\"h-4 w-4\" />\r\n </Button>\r\n </div>\r\n );\r\n};\r\n"],"mappings":"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"}
@@ -156,6 +156,15 @@ const defaultFilterMethods = {
156
156
  value: "equals",
157
157
  label: "Equals",
158
158
  component: "select"
159
+ }],
160
+ jsonArray: [{
161
+ value: "oneOf",
162
+ label: "One Of",
163
+ component: "multiSelect"
164
+ }, {
165
+ value: "equals",
166
+ label: "Equals",
167
+ component: "select"
159
168
  }]
160
169
  };
161
170
  const SINGLE_FILTER_KEY = "single-filter";
@@ -164,7 +173,8 @@ const mergeFilterMethods = (overrides) => ({
164
173
  number: overrides?.number && overrides.number.length > 0 ? overrides.number : defaultFilterMethods.number,
165
174
  date: overrides?.date && overrides.date.length > 0 ? overrides.date : defaultFilterMethods.date,
166
175
  boolean: overrides?.boolean && overrides.boolean.length > 0 ? overrides.boolean : defaultFilterMethods.boolean,
167
- enum: overrides?.enum && overrides.enum.length > 0 ? overrides.enum : defaultFilterMethods.enum
176
+ enum: overrides?.enum && overrides.enum.length > 0 ? overrides.enum : defaultFilterMethods.enum,
177
+ jsonArray: overrides?.jsonArray && overrides.jsonArray.length > 0 ? overrides.jsonArray : defaultFilterMethods.jsonArray
168
178
  });
169
179
  const TableFiltering = ({ columns, onFiltersChange, filters: initialFilters = [], onClose, singleFilter = false, filterMethods }) => {
170
180
  const [filters, setFilters] = useState({});
@@ -1 +1 @@
1
- {"version":3,"file":"TableFiltering.mjs","names":["Button"],"sources":["../../../../../src/modules/table/components/TableFiltering.tsx"],"sourcesContent":["import {\r\n DatePicker,\r\n DateRangePicker,\r\n type DateValue,\r\n Input,\r\n Select,\r\n SelectItem,\r\n type SharedSelection,\r\n} from \"@heroui/react\";\r\nimport { getLocalTimeZone, today } from \"@internationalized/date\";\r\nimport type { QueryFilters } from \"@m5kdev/commons/modules/schemas/query.schema\";\r\nimport type {\r\n ColumnDataType,\r\n ComponentForFilterMethod,\r\n FilterMethod,\r\n FilterMethods,\r\n} from \"@m5kdev/commons/modules/table/filter.types\";\r\nimport { PlusIcon, XIcon } from \"lucide-react\";\r\nimport type { ReactNode } from \"react\";\r\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\r\nimport { Button } from \"../../../components/ui/button\";\r\nimport {\r\n type FilterValue,\r\n type HeroUIFilter,\r\n transformFiltersFromHeroUI,\r\n transformFiltersToHeroUI,\r\n} from \"../filterTransformers\";\r\n\r\ntype ComponentRenderer = (\r\n value: FilterValue,\r\n onChange: (value: FilterValue) => void,\r\n options?: { label: string; value: string }[]\r\n) => ReactNode;\r\n\r\nconst componentForFilterMethod: Record<ComponentForFilterMethod, ComponentRenderer> = {\r\n text: (value, onChange) => (\r\n <Input\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n value={(value as string) ?? \"\"}\r\n onChange={(e) => onChange(e.target.value)}\r\n />\r\n ),\r\n number: (value, onChange) => (\r\n <Input\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n type=\"number\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n value={(value as number | null)?.toString() ?? \"\"}\r\n onChange={(e) => onChange(Number.parseFloat(e.target.value) || 0)}\r\n />\r\n ),\r\n date: (value, onChange) => (\r\n <DatePicker\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n value={(value as any) ?? undefined}\r\n onChange={(date) => date && onChange(date as FilterValue)}\r\n maxValue={today(getLocalTimeZone()) as unknown as DateValue}\r\n />\r\n ),\r\n range: (value, onChange) => (\r\n <DateRangePicker\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n value={(value as any) ?? undefined}\r\n onChange={(range) => range && onChange(range as FilterValue)}\r\n maxValue={today(getLocalTimeZone()) as unknown as DateValue}\r\n />\r\n ),\r\n radio: (value, onChange) => (\r\n <Select\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n selectedKeys={(value as boolean | null) ? [\"true\"] : [\"false\"]}\r\n onSelectionChange={(keys) => onChange(keys.currentKey === \"true\")}\r\n >\r\n <SelectItem key=\"true\" className=\"text-sm\">\r\n True\r\n </SelectItem>\r\n <SelectItem key=\"false\" className=\"text-sm\">\r\n False\r\n </SelectItem>\r\n </Select>\r\n ),\r\n select: (value, onChange, options = []) => (\r\n <Select\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n selectedKeys={(value as SharedSelection) ?? new Set()}\r\n onSelectionChange={(keys) => keys && onChange(keys as FilterValue)}\r\n >\r\n {options.map((option) => (\r\n <SelectItem key={option.value} className=\"text-sm\">\r\n {option.label}\r\n </SelectItem>\r\n ))}\r\n </Select>\r\n ),\r\n multiSelect: (value, onChange, options = []) => (\r\n <Select\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n selectionMode=\"multiple\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n selectedKeys={value ? new Set(value as SharedSelection) : new Set()}\r\n onSelectionChange={(keys) => onChange(keys as FilterValue)}\r\n >\r\n {options.map((option) => (\r\n <SelectItem key={option.value} className=\"text-sm\">\r\n {option.label}\r\n </SelectItem>\r\n ))}\r\n </Select>\r\n ),\r\n};\r\n\r\nconst defaultFilterMethods: FilterMethods = {\r\n string: [\r\n { value: \"contains\", label: \"Contains\", component: \"text\" },\r\n { value: \"equals\", label: \"Equals\", component: \"text\" },\r\n { value: \"starts_with\", label: \"Starts With\", component: \"text\" },\r\n { value: \"ends_with\", label: \"Ends With\", component: \"text\" },\r\n ],\r\n number: [\r\n { value: \"equals\", label: \"Equals\", component: \"number\" },\r\n { value: \"greater_than\", label: \"Greater Than\", component: \"number\" },\r\n { value: \"less_than\", label: \"Less Than\", component: \"number\" },\r\n ],\r\n date: [\r\n { value: \"on\", label: \"On\", component: \"date\" },\r\n { value: \"between\", label: \"Between\", component: \"range\" },\r\n { value: \"before\", label: \"Before\", component: \"date\" },\r\n { value: \"after\", label: \"After\", component: \"date\" },\r\n { value: \"intersect\", label: \"During\", component: \"range\" },\r\n ],\r\n boolean: [{ value: \"equals\", label: \"Equals\", component: \"radio\" }],\r\n enum: [\r\n { value: \"oneOf\", label: \"One Of\", component: \"multiSelect\" },\r\n { value: \"equals\", label: \"Equals\", component: \"select\" },\r\n ],\r\n};\r\n\r\nconst SINGLE_FILTER_KEY = \"single-filter\";\r\n\r\ntype TableFilteringProps = {\r\n columns: {\r\n id: string;\r\n label: string;\r\n type?: ColumnDataType | null;\r\n options?: { label: string; value: string }[] | null;\r\n endColumnId?: string | null;\r\n periodStartColumnId?: string | null;\r\n periodEndColumnId?: string | null;\r\n }[];\r\n onFiltersChange: (filters: QueryFilters) => void;\r\n filters: QueryFilters;\r\n onClose?: () => void;\r\n singleFilter?: boolean;\r\n filterMethods?: Partial<FilterMethods>;\r\n};\r\n\r\nconst mergeFilterMethods = (overrides?: Partial<FilterMethods>): FilterMethods => ({\r\n string:\r\n overrides?.string && overrides.string.length > 0\r\n ? overrides.string\r\n : defaultFilterMethods.string,\r\n number:\r\n overrides?.number && overrides.number.length > 0\r\n ? overrides.number\r\n : defaultFilterMethods.number,\r\n date: overrides?.date && overrides.date.length > 0 ? overrides.date : defaultFilterMethods.date,\r\n boolean:\r\n overrides?.boolean && overrides.boolean.length > 0\r\n ? overrides.boolean\r\n : defaultFilterMethods.boolean,\r\n enum: overrides?.enum && overrides.enum.length > 0 ? overrides.enum : defaultFilterMethods.enum,\r\n});\r\n\r\nexport const TableFiltering = ({\r\n columns,\r\n onFiltersChange,\r\n filters: initialFilters = [],\r\n onClose,\r\n singleFilter = false,\r\n filterMethods,\r\n}: TableFilteringProps) => {\r\n const [filters, setFilters] = useState<Record<string, HeroUIFilter>>({});\r\n const effectiveFilterMethods = useMemo(() => mergeFilterMethods(filterMethods), [filterMethods]);\r\n\r\n const createEmptyFilter = useCallback(\r\n (): HeroUIFilter => ({\r\n columnId: \"\",\r\n type: null,\r\n value: null,\r\n method: null,\r\n options: null,\r\n endColumnId: null,\r\n periodStartColumnId: null,\r\n periodEndColumnId: null,\r\n }),\r\n []\r\n );\r\n\r\n const addFilter = useCallback(() => {\r\n setFilters((prev) => {\r\n if (!singleFilter) {\r\n const filterId = crypto.randomUUID();\r\n return {\r\n ...prev,\r\n [filterId]: createEmptyFilter(),\r\n };\r\n }\r\n\r\n // single filter mode\r\n return Object.keys(prev).length > 0 ? prev : { [SINGLE_FILTER_KEY]: createEmptyFilter() };\r\n });\r\n }, [createEmptyFilter, singleFilter]);\r\n\r\n const removeFilter = useCallback(\r\n (filterId: string) => {\r\n setFilters((prev) => {\r\n if (!singleFilter) {\r\n const { [filterId]: _, ...rest } = prev;\r\n return rest;\r\n }\r\n\r\n // single filter mode resets the lone filter\r\n return { [SINGLE_FILTER_KEY]: createEmptyFilter() };\r\n });\r\n },\r\n [createEmptyFilter, singleFilter]\r\n );\r\n\r\n const columnsMap = useMemo(\r\n () => new Map(columns.map((column) => [column.id, column])),\r\n [columns]\r\n );\r\n\r\n useEffect(() => {\r\n // Transform initialFilters from FiltersToApply format to HeroUIFilter format\r\n const transformedFilters = transformFiltersToHeroUI(\r\n initialFilters,\r\n columnsMap,\r\n effectiveFilterMethods\r\n );\r\n\r\n if (!singleFilter) {\r\n setFilters(Object.fromEntries(transformedFilters.map((filter) => [filter.columnId, filter])));\r\n return;\r\n }\r\n\r\n const firstFilter = transformedFilters[0];\r\n setFilters({\r\n [SINGLE_FILTER_KEY]: firstFilter ?? createEmptyFilter(),\r\n });\r\n }, [createEmptyFilter, initialFilters, singleFilter, columnsMap, effectiveFilterMethods]);\r\n\r\n const selectColumn = useCallback(\r\n (filterId: string, columnId: string) => {\r\n setFilters((prev) => {\r\n const oldFilter = prev[filterId];\r\n if (!oldFilter) return prev;\r\n const column = columnsMap.get(columnId);\r\n if (!column) return prev;\r\n\r\n // If Period column, auto-set intersect method\r\n const isPeriodColumn = columnId.endsWith(\"__period\");\r\n const intersectMethod = isPeriodColumn\r\n ? (effectiveFilterMethods.date.find((m) => m.value === \"intersect\") ?? null)\r\n : null;\r\n\r\n return {\r\n ...prev,\r\n [filterId]: {\r\n ...oldFilter,\r\n columnId,\r\n type: column.type ?? null,\r\n options: column.options ?? null,\r\n endColumnId: column.endColumnId ?? null,\r\n periodStartColumnId: column.periodStartColumnId ?? null,\r\n periodEndColumnId: column.periodEndColumnId ?? null,\r\n method: intersectMethod,\r\n value: null,\r\n },\r\n };\r\n });\r\n },\r\n [columnsMap, effectiveFilterMethods]\r\n );\r\n\r\n const selectMethod = useCallback((filterId: string, method: FilterMethod) => {\r\n setFilters((prev) => {\r\n const oldFilter = prev[filterId];\r\n if (!oldFilter) return prev;\r\n return {\r\n ...prev,\r\n [filterId]: { ...oldFilter, method, value: null },\r\n };\r\n });\r\n }, []);\r\n\r\n const selectValue = useCallback((filterId: string, value: FilterValue) => {\r\n setFilters((prev) => {\r\n const oldFilter = prev[filterId];\r\n if (!oldFilter) return prev;\r\n return {\r\n ...prev,\r\n [filterId]: { ...oldFilter, value },\r\n };\r\n });\r\n }, []);\r\n\r\n const filterEntries = useMemo(() => Object.entries(filters), [filters]);\r\n\r\n const availableColumnsMap = useMemo(() => {\r\n const map = new Map<string, typeof columns>();\r\n filterEntries.forEach(([filterId]) => {\r\n const columnsUsedByOtherFilters = new Set(\r\n filterEntries\r\n .filter(([id]) => id !== filterId)\r\n .map(([_, f]) => f.columnId)\r\n .filter((id) => id !== \"\")\r\n );\r\n map.set(\r\n filterId,\r\n columns.filter((column) => !columnsUsedByOtherFilters.has(column.id))\r\n );\r\n });\r\n return map;\r\n }, [filterEntries, columns]);\r\n\r\n const applyFilters = useCallback(() => {\r\n const heroUIFilters = Object.values(filters);\r\n const filtersToApply = transformFiltersFromHeroUI(heroUIFilters);\r\n onFiltersChange(filtersToApply);\r\n onClose?.();\r\n }, [filters, onFiltersChange, onClose]);\r\n\r\n return (\r\n <div className=\"flex flex-col gap-2 p-1 min-w-[600px]\">\r\n {filterEntries.map(([filterId, filter]) => {\r\n const availableColumns = availableColumnsMap.get(filterId) ?? columns;\r\n return (\r\n <TableFilteringItem\r\n key={filterId}\r\n id={filterId}\r\n filter={filter}\r\n columns={availableColumns}\r\n selectColumn={selectColumn}\r\n selectMethod={selectMethod}\r\n removeFilter={removeFilter}\r\n selectValue={selectValue}\r\n filterMethods={effectiveFilterMethods}\r\n />\r\n );\r\n })}\r\n {!singleFilter && (\r\n <Button variant=\"outline\" size=\"sm\" onClick={addFilter}>\r\n <PlusIcon className=\"h-4 w-4\" />\r\n Add Filter\r\n </Button>\r\n )}\r\n <Button onClick={applyFilters}>{singleFilter ? \"Apply Filter\" : \"Apply Filters\"}</Button>\r\n </div>\r\n );\r\n};\r\n\r\nconst TableFilteringItem = ({\r\n id,\r\n filter,\r\n columns,\r\n selectColumn,\r\n selectMethod,\r\n selectValue,\r\n removeFilter,\r\n filterMethods,\r\n}: {\r\n id: string;\r\n filter: HeroUIFilter;\r\n columns: {\r\n id: string;\r\n label: string;\r\n type?: ColumnDataType | null;\r\n options?: { label: string; value: string }[] | null;\r\n endColumnId?: string | null;\r\n periodStartColumnId?: string | null;\r\n periodEndColumnId?: string | null;\r\n }[];\r\n selectColumn: (filterId: string, columnId: string) => void;\r\n selectMethod: (filterId: string, method: FilterMethod) => void;\r\n selectValue: (filterId: string, value: FilterValue) => void;\r\n removeFilter: (filterId: string) => void;\r\n filterMethods: FilterMethods;\r\n}) => {\r\n const handleColumnChange = useCallback(\r\n (keys: any) => {\r\n const columnId = String(keys.currentKey);\r\n selectColumn(id, columnId);\r\n },\r\n [id, selectColumn]\r\n );\r\n\r\n const methodsForType = useMemo(() => {\r\n if (!filter.type) return [];\r\n\r\n // Period columns only support intersect method\r\n const isPeriodColumn = filter.columnId.endsWith(\"__period\");\r\n if (isPeriodColumn) {\r\n const intersectMethod = filterMethods.date.find((m) => m.value === \"intersect\");\r\n return intersectMethod ? [intersectMethod] : [];\r\n }\r\n\r\n const baseMethods = filterMethods[filter.type as ColumnDataType] ?? [];\r\n const emptyMethods: FilterMethod[] = [\r\n { value: \"isEmpty\", label: \"Is Empty\", component: null },\r\n { value: \"isNotEmpty\", label: \"Is Not Empty\", component: null },\r\n ];\r\n\r\n if (filter.type !== \"boolean\" && filter.type !== \"date\") {\r\n return [...baseMethods, ...emptyMethods];\r\n }\r\n\r\n return baseMethods;\r\n }, [filter.type, filter.columnId, filter.method?.value, filterMethods]);\r\n\r\n const handleMethodChange = useCallback(\r\n (keys: any) => {\r\n if (!filter.type) return;\r\n // Use methodsForType instead of filterMethods to include dynamically added methods\r\n const method = methodsForType.find(\r\n (currentMethod: FilterMethod) => currentMethod.value === String(keys.currentKey)\r\n );\r\n if (method) {\r\n selectMethod(id, method);\r\n }\r\n },\r\n [id, filter.type, selectMethod, methodsForType]\r\n );\r\n\r\n const handleValueChange = useCallback(\r\n (value: FilterValue) => {\r\n selectValue(id, value);\r\n },\r\n [id, selectValue]\r\n );\r\n\r\n const methodSelect = useMemo(() => {\r\n if (!filter.type) return null;\r\n return (\r\n <Select\r\n size=\"sm\"\r\n aria-label=\"Select Method\"\r\n className=\"w-40 flex-shrink-0 text-sm\"\r\n selectedKeys={filter.method?.value ? [filter.method.value] : []}\r\n onSelectionChange={handleMethodChange}\r\n popoverProps={{\r\n className: \"w-auto min-w-max\",\r\n }}\r\n >\r\n {methodsForType.map((method: FilterMethod) => (\r\n <SelectItem key={method.value} className=\"text-sm\">\r\n {method.label}\r\n </SelectItem>\r\n ))}\r\n </Select>\r\n );\r\n }, [filter.type, filter.method?.value, methodsForType, handleMethodChange]);\r\n\r\n const filterValueComponent = useMemo(() => {\r\n if (!filter.method?.component) {\r\n return <div className=\"flex-1 min-w-0\" />;\r\n }\r\n const component = filter.method.component as ComponentForFilterMethod;\r\n const ComponentFn =\r\n componentForFilterMethod[component as keyof typeof componentForFilterMethod];\r\n if (!ComponentFn) return <div className=\"flex-1 min-w-0\" />;\r\n return ComponentFn(filter.value as any, handleValueChange, filter.options ?? []);\r\n }, [filter.method?.component, filter.value, filter.options, handleValueChange]);\r\n\r\n const columnSelectItems = useMemo(\r\n () =>\r\n columns.map((column) => (\r\n <SelectItem key={column.id} className=\"text-sm\">\r\n {String(column.label)}\r\n </SelectItem>\r\n )),\r\n [columns]\r\n );\r\n\r\n return (\r\n <div className=\"flex items-center gap-2 w-full\">\r\n <div className=\"flex flex-1 items-center gap-2 min-w-0\">\r\n <Select\r\n size=\"sm\"\r\n aria-label=\"Select Column\"\r\n className=\"w-40 flex-shrink-0 text-sm\"\r\n selectedKeys={filter.columnId ? [filter.columnId] : []}\r\n onSelectionChange={handleColumnChange}\r\n popoverProps={{\r\n className: \"w-auto min-w-max\",\r\n }}\r\n >\r\n {columnSelectItems}\r\n </Select>\r\n {methodSelect}\r\n {filterValueComponent}\r\n </div>\r\n <Button variant=\"outline\" size=\"sm\" onClick={() => removeFilter(id)}>\r\n <XIcon className=\"h-4 w-4\" />\r\n </Button>\r\n </div>\r\n );\r\n};\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"TableFiltering.mjs","names":["Button"],"sources":["../../../../../src/modules/table/components/TableFiltering.tsx"],"sourcesContent":["import {\r\n DatePicker,\r\n DateRangePicker,\r\n type DateValue,\r\n Input,\r\n Select,\r\n SelectItem,\r\n type SharedSelection,\r\n} from \"@heroui/react\";\r\nimport { getLocalTimeZone, today } from \"@internationalized/date\";\r\nimport type { QueryFilters } from \"@m5kdev/commons/modules/schemas/query.schema\";\r\nimport type {\r\n ColumnDataType,\r\n ComponentForFilterMethod,\r\n FilterMethod,\r\n FilterMethods,\r\n} from \"@m5kdev/commons/modules/table/filter.types\";\r\nimport { PlusIcon, XIcon } from \"lucide-react\";\r\nimport type { ReactNode } from \"react\";\r\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\r\nimport { Button } from \"../../../components/ui/button\";\r\nimport {\r\n type FilterValue,\r\n type HeroUIFilter,\r\n transformFiltersFromHeroUI,\r\n transformFiltersToHeroUI,\r\n} from \"../filterTransformers\";\r\n\r\ntype ComponentRenderer = (\r\n value: FilterValue,\r\n onChange: (value: FilterValue) => void,\r\n options?: { label: string; value: string }[]\r\n) => ReactNode;\r\n\r\nconst componentForFilterMethod: Record<ComponentForFilterMethod, ComponentRenderer> = {\r\n text: (value, onChange) => (\r\n <Input\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n value={(value as string) ?? \"\"}\r\n onChange={(e) => onChange(e.target.value)}\r\n />\r\n ),\r\n number: (value, onChange) => (\r\n <Input\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n type=\"number\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n value={(value as number | null)?.toString() ?? \"\"}\r\n onChange={(e) => onChange(Number.parseFloat(e.target.value) || 0)}\r\n />\r\n ),\r\n date: (value, onChange) => (\r\n <DatePicker\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n value={(value as any) ?? undefined}\r\n onChange={(date) => date && onChange(date as FilterValue)}\r\n maxValue={today(getLocalTimeZone()) as unknown as DateValue}\r\n />\r\n ),\r\n range: (value, onChange) => (\r\n <DateRangePicker\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n value={(value as any) ?? undefined}\r\n onChange={(range) => range && onChange(range as FilterValue)}\r\n maxValue={today(getLocalTimeZone()) as unknown as DateValue}\r\n />\r\n ),\r\n radio: (value, onChange) => (\r\n <Select\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n selectedKeys={(value as boolean | null) ? [\"true\"] : [\"false\"]}\r\n onSelectionChange={(keys) => onChange(keys.currentKey === \"true\")}\r\n >\r\n <SelectItem key=\"true\" className=\"text-sm\">\r\n True\r\n </SelectItem>\r\n <SelectItem key=\"false\" className=\"text-sm\">\r\n False\r\n </SelectItem>\r\n </Select>\r\n ),\r\n select: (value, onChange, options = []) => (\r\n <Select\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n selectedKeys={(value as SharedSelection) ?? new Set()}\r\n onSelectionChange={(keys) => keys && onChange(keys as FilterValue)}\r\n >\r\n {options.map((option) => (\r\n <SelectItem key={option.value} className=\"text-sm\">\r\n {option.label}\r\n </SelectItem>\r\n ))}\r\n </Select>\r\n ),\r\n multiSelect: (value, onChange, options = []) => (\r\n <Select\r\n size=\"sm\"\r\n aria-label=\"Select Value\"\r\n selectionMode=\"multiple\"\r\n className=\"flex-1 min-w-0 text-sm\"\r\n selectedKeys={value ? new Set(value as SharedSelection) : new Set()}\r\n onSelectionChange={(keys) => onChange(keys as FilterValue)}\r\n >\r\n {options.map((option) => (\r\n <SelectItem key={option.value} className=\"text-sm\">\r\n {option.label}\r\n </SelectItem>\r\n ))}\r\n </Select>\r\n ),\r\n};\r\n\r\nconst defaultFilterMethods: FilterMethods = {\r\n string: [\r\n { value: \"contains\", label: \"Contains\", component: \"text\" },\r\n { value: \"equals\", label: \"Equals\", component: \"text\" },\r\n { value: \"starts_with\", label: \"Starts With\", component: \"text\" },\r\n { value: \"ends_with\", label: \"Ends With\", component: \"text\" },\r\n ],\r\n number: [\r\n { value: \"equals\", label: \"Equals\", component: \"number\" },\r\n { value: \"greater_than\", label: \"Greater Than\", component: \"number\" },\r\n { value: \"less_than\", label: \"Less Than\", component: \"number\" },\r\n ],\r\n date: [\r\n { value: \"on\", label: \"On\", component: \"date\" },\r\n { value: \"between\", label: \"Between\", component: \"range\" },\r\n { value: \"before\", label: \"Before\", component: \"date\" },\r\n { value: \"after\", label: \"After\", component: \"date\" },\r\n { value: \"intersect\", label: \"During\", component: \"range\" },\r\n ],\r\n boolean: [{ value: \"equals\", label: \"Equals\", component: \"radio\" }],\r\n enum: [\r\n { value: \"oneOf\", label: \"One Of\", component: \"multiSelect\" },\r\n { value: \"equals\", label: \"Equals\", component: \"select\" },\r\n ],\r\n jsonArray: [\r\n { value: \"oneOf\", label: \"One Of\", component: \"multiSelect\" },\r\n { value: \"equals\", label: \"Equals\", component: \"select\" },\r\n ],\r\n};\r\n\r\nconst SINGLE_FILTER_KEY = \"single-filter\";\r\n\r\ntype TableFilteringProps = {\r\n columns: {\r\n id: string;\r\n label: string;\r\n type?: ColumnDataType | null;\r\n options?: { label: string; value: string }[] | null;\r\n endColumnId?: string | null;\r\n periodStartColumnId?: string | null;\r\n periodEndColumnId?: string | null;\r\n }[];\r\n onFiltersChange: (filters: QueryFilters) => void;\r\n filters: QueryFilters;\r\n onClose?: () => void;\r\n singleFilter?: boolean;\r\n filterMethods?: Partial<FilterMethods>;\r\n};\r\n\r\nconst mergeFilterMethods = (overrides?: Partial<FilterMethods>): FilterMethods => ({\r\n string:\r\n overrides?.string && overrides.string.length > 0\r\n ? overrides.string\r\n : defaultFilterMethods.string,\r\n number:\r\n overrides?.number && overrides.number.length > 0\r\n ? overrides.number\r\n : defaultFilterMethods.number,\r\n date: overrides?.date && overrides.date.length > 0 ? overrides.date : defaultFilterMethods.date,\r\n boolean:\r\n overrides?.boolean && overrides.boolean.length > 0\r\n ? overrides.boolean\r\n : defaultFilterMethods.boolean,\r\n enum: overrides?.enum && overrides.enum.length > 0 ? overrides.enum : defaultFilterMethods.enum,\r\n jsonArray:\r\n overrides?.jsonArray && overrides.jsonArray.length > 0\r\n ? overrides.jsonArray\r\n : defaultFilterMethods.jsonArray,\r\n});\r\n\r\nexport const TableFiltering = ({\r\n columns,\r\n onFiltersChange,\r\n filters: initialFilters = [],\r\n onClose,\r\n singleFilter = false,\r\n filterMethods,\r\n}: TableFilteringProps) => {\r\n const [filters, setFilters] = useState<Record<string, HeroUIFilter>>({});\r\n const effectiveFilterMethods = useMemo(() => mergeFilterMethods(filterMethods), [filterMethods]);\r\n\r\n const createEmptyFilter = useCallback(\r\n (): HeroUIFilter => ({\r\n columnId: \"\",\r\n type: null,\r\n value: null,\r\n method: null,\r\n options: null,\r\n endColumnId: null,\r\n periodStartColumnId: null,\r\n periodEndColumnId: null,\r\n }),\r\n []\r\n );\r\n\r\n const addFilter = useCallback(() => {\r\n setFilters((prev) => {\r\n if (!singleFilter) {\r\n const filterId = crypto.randomUUID();\r\n return {\r\n ...prev,\r\n [filterId]: createEmptyFilter(),\r\n };\r\n }\r\n\r\n // single filter mode\r\n return Object.keys(prev).length > 0 ? prev : { [SINGLE_FILTER_KEY]: createEmptyFilter() };\r\n });\r\n }, [createEmptyFilter, singleFilter]);\r\n\r\n const removeFilter = useCallback(\r\n (filterId: string) => {\r\n setFilters((prev) => {\r\n if (!singleFilter) {\r\n const { [filterId]: _, ...rest } = prev;\r\n return rest;\r\n }\r\n\r\n // single filter mode resets the lone filter\r\n return { [SINGLE_FILTER_KEY]: createEmptyFilter() };\r\n });\r\n },\r\n [createEmptyFilter, singleFilter]\r\n );\r\n\r\n const columnsMap = useMemo(\r\n () => new Map(columns.map((column) => [column.id, column])),\r\n [columns]\r\n );\r\n\r\n useEffect(() => {\r\n // Transform initialFilters from FiltersToApply format to HeroUIFilter format\r\n const transformedFilters = transformFiltersToHeroUI(\r\n initialFilters,\r\n columnsMap,\r\n effectiveFilterMethods\r\n );\r\n\r\n if (!singleFilter) {\r\n setFilters(Object.fromEntries(transformedFilters.map((filter) => [filter.columnId, filter])));\r\n return;\r\n }\r\n\r\n const firstFilter = transformedFilters[0];\r\n setFilters({\r\n [SINGLE_FILTER_KEY]: firstFilter ?? createEmptyFilter(),\r\n });\r\n }, [createEmptyFilter, initialFilters, singleFilter, columnsMap, effectiveFilterMethods]);\r\n\r\n const selectColumn = useCallback(\r\n (filterId: string, columnId: string) => {\r\n setFilters((prev) => {\r\n const oldFilter = prev[filterId];\r\n if (!oldFilter) return prev;\r\n const column = columnsMap.get(columnId);\r\n if (!column) return prev;\r\n\r\n // If Period column, auto-set intersect method\r\n const isPeriodColumn = columnId.endsWith(\"__period\");\r\n const intersectMethod = isPeriodColumn\r\n ? (effectiveFilterMethods.date.find((m) => m.value === \"intersect\") ?? null)\r\n : null;\r\n\r\n return {\r\n ...prev,\r\n [filterId]: {\r\n ...oldFilter,\r\n columnId,\r\n type: column.type ?? null,\r\n options: column.options ?? null,\r\n endColumnId: column.endColumnId ?? null,\r\n periodStartColumnId: column.periodStartColumnId ?? null,\r\n periodEndColumnId: column.periodEndColumnId ?? null,\r\n method: intersectMethod,\r\n value: null,\r\n },\r\n };\r\n });\r\n },\r\n [columnsMap, effectiveFilterMethods]\r\n );\r\n\r\n const selectMethod = useCallback((filterId: string, method: FilterMethod) => {\r\n setFilters((prev) => {\r\n const oldFilter = prev[filterId];\r\n if (!oldFilter) return prev;\r\n return {\r\n ...prev,\r\n [filterId]: { ...oldFilter, method, value: null },\r\n };\r\n });\r\n }, []);\r\n\r\n const selectValue = useCallback((filterId: string, value: FilterValue) => {\r\n setFilters((prev) => {\r\n const oldFilter = prev[filterId];\r\n if (!oldFilter) return prev;\r\n return {\r\n ...prev,\r\n [filterId]: { ...oldFilter, value },\r\n };\r\n });\r\n }, []);\r\n\r\n const filterEntries = useMemo(() => Object.entries(filters), [filters]);\r\n\r\n const availableColumnsMap = useMemo(() => {\r\n const map = new Map<string, typeof columns>();\r\n filterEntries.forEach(([filterId]) => {\r\n const columnsUsedByOtherFilters = new Set(\r\n filterEntries\r\n .filter(([id]) => id !== filterId)\r\n .map(([_, f]) => f.columnId)\r\n .filter((id) => id !== \"\")\r\n );\r\n map.set(\r\n filterId,\r\n columns.filter((column) => !columnsUsedByOtherFilters.has(column.id))\r\n );\r\n });\r\n return map;\r\n }, [filterEntries, columns]);\r\n\r\n const applyFilters = useCallback(() => {\r\n const heroUIFilters = Object.values(filters);\r\n const filtersToApply = transformFiltersFromHeroUI(heroUIFilters);\r\n onFiltersChange(filtersToApply);\r\n onClose?.();\r\n }, [filters, onFiltersChange, onClose]);\r\n\r\n return (\r\n <div className=\"flex flex-col gap-2 p-1 min-w-[600px]\">\r\n {filterEntries.map(([filterId, filter]) => {\r\n const availableColumns = availableColumnsMap.get(filterId) ?? columns;\r\n return (\r\n <TableFilteringItem\r\n key={filterId}\r\n id={filterId}\r\n filter={filter}\r\n columns={availableColumns}\r\n selectColumn={selectColumn}\r\n selectMethod={selectMethod}\r\n removeFilter={removeFilter}\r\n selectValue={selectValue}\r\n filterMethods={effectiveFilterMethods}\r\n />\r\n );\r\n })}\r\n {!singleFilter && (\r\n <Button variant=\"outline\" size=\"sm\" onClick={addFilter}>\r\n <PlusIcon className=\"h-4 w-4\" />\r\n Add Filter\r\n </Button>\r\n )}\r\n <Button onClick={applyFilters}>{singleFilter ? \"Apply Filter\" : \"Apply Filters\"}</Button>\r\n </div>\r\n );\r\n};\r\n\r\nconst TableFilteringItem = ({\r\n id,\r\n filter,\r\n columns,\r\n selectColumn,\r\n selectMethod,\r\n selectValue,\r\n removeFilter,\r\n filterMethods,\r\n}: {\r\n id: string;\r\n filter: HeroUIFilter;\r\n columns: {\r\n id: string;\r\n label: string;\r\n type?: ColumnDataType | null;\r\n options?: { label: string; value: string }[] | null;\r\n endColumnId?: string | null;\r\n periodStartColumnId?: string | null;\r\n periodEndColumnId?: string | null;\r\n }[];\r\n selectColumn: (filterId: string, columnId: string) => void;\r\n selectMethod: (filterId: string, method: FilterMethod) => void;\r\n selectValue: (filterId: string, value: FilterValue) => void;\r\n removeFilter: (filterId: string) => void;\r\n filterMethods: FilterMethods;\r\n}) => {\r\n const handleColumnChange = useCallback(\r\n (keys: any) => {\r\n const columnId = String(keys.currentKey);\r\n selectColumn(id, columnId);\r\n },\r\n [id, selectColumn]\r\n );\r\n\r\n const methodsForType = useMemo(() => {\r\n if (!filter.type) return [];\r\n\r\n // Period columns only support intersect method\r\n const isPeriodColumn = filter.columnId.endsWith(\"__period\");\r\n if (isPeriodColumn) {\r\n const intersectMethod = filterMethods.date.find((m) => m.value === \"intersect\");\r\n return intersectMethod ? [intersectMethod] : [];\r\n }\r\n\r\n const baseMethods = filterMethods[filter.type as ColumnDataType] ?? [];\r\n const emptyMethods: FilterMethod[] = [\r\n { value: \"isEmpty\", label: \"Is Empty\", component: null },\r\n { value: \"isNotEmpty\", label: \"Is Not Empty\", component: null },\r\n ];\r\n\r\n if (filter.type !== \"boolean\" && filter.type !== \"date\") {\r\n return [...baseMethods, ...emptyMethods];\r\n }\r\n\r\n return baseMethods;\r\n }, [filter.type, filter.columnId, filter.method?.value, filterMethods]);\r\n\r\n const handleMethodChange = useCallback(\r\n (keys: any) => {\r\n if (!filter.type) return;\r\n // Use methodsForType instead of filterMethods to include dynamically added methods\r\n const method = methodsForType.find(\r\n (currentMethod: FilterMethod) => currentMethod.value === String(keys.currentKey)\r\n );\r\n if (method) {\r\n selectMethod(id, method);\r\n }\r\n },\r\n [id, filter.type, selectMethod, methodsForType]\r\n );\r\n\r\n const handleValueChange = useCallback(\r\n (value: FilterValue) => {\r\n selectValue(id, value);\r\n },\r\n [id, selectValue]\r\n );\r\n\r\n const methodSelect = useMemo(() => {\r\n if (!filter.type) return null;\r\n return (\r\n <Select\r\n size=\"sm\"\r\n aria-label=\"Select Method\"\r\n className=\"w-40 flex-shrink-0 text-sm\"\r\n selectedKeys={filter.method?.value ? [filter.method.value] : []}\r\n onSelectionChange={handleMethodChange}\r\n popoverProps={{\r\n className: \"w-auto min-w-max\",\r\n }}\r\n >\r\n {methodsForType.map((method: FilterMethod) => (\r\n <SelectItem key={method.value} className=\"text-sm\">\r\n {method.label}\r\n </SelectItem>\r\n ))}\r\n </Select>\r\n );\r\n }, [filter.type, filter.method?.value, methodsForType, handleMethodChange]);\r\n\r\n const filterValueComponent = useMemo(() => {\r\n if (!filter.method?.component) {\r\n return <div className=\"flex-1 min-w-0\" />;\r\n }\r\n const component = filter.method.component as ComponentForFilterMethod;\r\n const ComponentFn =\r\n componentForFilterMethod[component as keyof typeof componentForFilterMethod];\r\n if (!ComponentFn) return <div className=\"flex-1 min-w-0\" />;\r\n return ComponentFn(filter.value as any, handleValueChange, filter.options ?? []);\r\n }, [filter.method?.component, filter.value, filter.options, handleValueChange]);\r\n\r\n const columnSelectItems = useMemo(\r\n () =>\r\n columns.map((column) => (\r\n <SelectItem key={column.id} className=\"text-sm\">\r\n {String(column.label)}\r\n </SelectItem>\r\n )),\r\n [columns]\r\n );\r\n\r\n return (\r\n <div className=\"flex items-center gap-2 w-full\">\r\n <div className=\"flex flex-1 items-center gap-2 min-w-0\">\r\n <Select\r\n size=\"sm\"\r\n aria-label=\"Select Column\"\r\n className=\"w-40 flex-shrink-0 text-sm\"\r\n selectedKeys={filter.columnId ? [filter.columnId] : []}\r\n onSelectionChange={handleColumnChange}\r\n popoverProps={{\r\n className: \"w-auto min-w-max\",\r\n }}\r\n >\r\n {columnSelectItems}\r\n </Select>\r\n {methodSelect}\r\n {filterValueComponent}\r\n </div>\r\n <Button variant=\"outline\" size=\"sm\" onClick={() => removeFilter(id)}>\r\n <XIcon className=\"h-4 w-4\" />\r\n </Button>\r\n </div>\r\n );\r\n};\r\n"],"mappings":"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"}
@@ -136,6 +136,20 @@ const transformFiltersToHeroUI = (filtersToTransform, columnsMap, filterMethods)
136
136
  value = selection;
137
137
  }
138
138
  break;
139
+ case "jsonArray":
140
+ if (filter.method === "oneOf" && Array.isArray(filter.value)) value = new Set(filter.value);
141
+ else if (Array.isArray(filter.value) && filter.value.length > 0) {
142
+ const first = filter.value[0];
143
+ const selection = new Set([first]);
144
+ Object.defineProperty(selection, "currentKey", {
145
+ value: first,
146
+ writable: true,
147
+ enumerable: false,
148
+ configurable: true
149
+ });
150
+ value = selection;
151
+ } else value = filter.value;
152
+ break;
139
153
  default: value = filter.value;
140
154
  }
141
155
  const column = columnsMap.get(columnId);
@@ -192,10 +206,15 @@ const transformFiltersFromHeroUI = (filters) => {
192
206
  if (selection) value = filter.method?.value === "oneOf" ? Array.from(selection).map(String) : String(selection.currentKey);
193
207
  break;
194
208
  }
209
+ case "jsonArray": {
210
+ const selection = filter.value;
211
+ if (selection) value = filter.method?.value === "oneOf" ? Array.from(selection).map(String) : [String(selection.currentKey)];
212
+ break;
213
+ }
195
214
  default: value = filter.value;
196
215
  }
197
216
  if (!value || value === "" || filter.columnId === "") continue;
198
- if (filter.type === "enum" && Array.isArray(value) && value.length === 0) continue;
217
+ if ((filter.type === "enum" || filter.type === "jsonArray") && Array.isArray(value) && value.length === 0) continue;
199
218
  const actualMethod = isPeriodColumn ? "intersect" : filter.method.value;
200
219
  const result = {
201
220
  columnId: actualColumnId,
@@ -1 +1 @@
1
- {"version":3,"file":"filterTransformers.js","names":["DateTime","CalendarDate"],"sources":["../../../../src/modules/table/filterTransformers.ts"],"sourcesContent":["import type { DateValue, RangeValue, SharedSelection } from \"@heroui/react\";\r\nimport { CalendarDate, getLocalTimeZone, today } from \"@internationalized/date\";\r\nimport type { QueryFilter, QueryFilters } from \"@m5kdev/commons/modules/schemas/query.schema\";\r\nimport type { ColumnDataType, FilterMethod } from \"@m5kdev/commons/modules/table/filter.types\";\r\nimport { DateTime } from \"luxon\";\r\n\r\nexport type FilterValue =\r\n | string\r\n | number\r\n | string[]\r\n | DateValue\r\n | RangeValue<DateValue>\r\n | boolean\r\n | SharedSelection\r\n | null;\r\n\r\nexport interface HeroUIFilter {\r\n columnId: string;\r\n type: ColumnDataType | null;\r\n value: FilterValue;\r\n method: FilterMethod | null;\r\n options?: { label: string; value: string }[] | null;\r\n endColumnId?: string | null;\r\n periodStartColumnId?: string | null;\r\n periodEndColumnId?: string | null;\r\n}\r\n\r\n/**\r\n * Convert CalendarDate to UTC ISO string at midnight UTC\r\n */\r\nexport const calendarDateToUTC = (date: CalendarDate): string => {\r\n return DateTime.utc(date.year, date.month, date.day).toISO() ?? \"\";\r\n};\r\n\r\n/**\r\n * Convert CalendarDate to end of day UTC ISO string\r\n */\r\nexport const calendarDateToEndOfDayUTC = (date: CalendarDate): string => {\r\n return DateTime.utc(date.year, date.month, date.day).endOf(\"day\").toISO() ?? \"\";\r\n};\r\n\r\n/**\r\n * Parse UTC ISO string to CalendarDate (preserves UTC date, no timezone shift)\r\n */\r\nconst parseUTCToCalendarDate = (isoString: string): CalendarDate | null => {\r\n try {\r\n const dt = DateTime.fromISO(isoString, { zone: \"utc\" });\r\n if (!dt.isValid) return null;\r\n return new CalendarDate(dt.year, dt.month, dt.day);\r\n } catch {\r\n return null;\r\n }\r\n};\r\n\r\n/**\r\n * Convert any date filter method from URL to a RangeValue for DateRangePicker\r\n * Handles: on, before, after, between, intersect\r\n * Parses UTC ISO strings as UTC to avoid timezone shifts\r\n */\r\nexport const dateFilterToRangeValue = (\r\n filters: QueryFilters | undefined,\r\n columnId: string\r\n): RangeValue<DateValue> | null => {\r\n if (!filters) return null;\r\n\r\n const filter = filters.find((f) => f.columnId === columnId);\r\n if (!filter || filter.type !== \"date\") return null;\r\n\r\n const todayDate = today(getLocalTimeZone());\r\n const epochStart = new CalendarDate(1970, 1, 1);\r\n\r\n try {\r\n switch (filter.method) {\r\n case \"on\": {\r\n // Same day range\r\n if (typeof filter.value !== \"string\" || !filter.value) return null;\r\n const day = parseUTCToCalendarDate(filter.value);\r\n if (!day) return null;\r\n return {\r\n start: day as unknown as DateValue,\r\n end: day as unknown as DateValue,\r\n } as RangeValue<DateValue>;\r\n }\r\n case \"before\": {\r\n // [epochStart, selectedDay]\r\n if (typeof filter.value !== \"string\" || !filter.value) return null;\r\n const day = parseUTCToCalendarDate(filter.value);\r\n if (!day) return null;\r\n return {\r\n start: epochStart as unknown as DateValue,\r\n end: day as unknown as DateValue,\r\n } as RangeValue<DateValue>;\r\n }\r\n case \"after\": {\r\n // [selectedDay, today]\r\n if (typeof filter.value !== \"string\" || !filter.value) return null;\r\n const day = parseUTCToCalendarDate(filter.value);\r\n if (!day) return null;\r\n return {\r\n start: day as unknown as DateValue,\r\n end: todayDate as unknown as DateValue,\r\n } as RangeValue<DateValue>;\r\n }\r\n case \"between\":\r\n case \"intersect\": {\r\n // [value, valueTo]\r\n if (typeof filter.value !== \"string\" || !filter.value || !filter.valueTo) return null;\r\n const startDate = parseUTCToCalendarDate(filter.value);\r\n const endDate = parseUTCToCalendarDate(filter.valueTo);\r\n if (!startDate || !endDate) return null;\r\n return {\r\n start: startDate as unknown as DateValue,\r\n end: endDate as unknown as DateValue,\r\n } as RangeValue<DateValue>;\r\n }\r\n default:\r\n return null;\r\n }\r\n } catch {\r\n return null;\r\n }\r\n};\r\n\r\n/**\r\n * Transform filters from backend format (QueryFilter[]) to HeroUI format (HeroUIFilter[])\r\n * Used when loading filters from URL/backend to populate HeroUI components\r\n */\r\nexport const transformFiltersToHeroUI = (\r\n filtersToTransform: QueryFilters,\r\n columnsMap: Map<\r\n string,\r\n {\r\n id: string;\r\n label: string;\r\n type?: ColumnDataType | null;\r\n options?: { label: string; value: string }[] | null;\r\n endColumnId?: string | null;\r\n periodStartColumnId?: string | null;\r\n periodEndColumnId?: string | null;\r\n }\r\n >,\r\n filterMethods: Record<ColumnDataType, FilterMethod[]>\r\n): HeroUIFilter[] => {\r\n return filtersToTransform.map((filter) => {\r\n let value: FilterValue = null;\r\n let method: FilterMethod | null = null;\r\n let columnId = filter.columnId;\r\n\r\n // Check if this intersect filter should map to Period pseudo-column\r\n if (filter.type === \"date\" && filter.method === \"intersect\" && filter.endColumnId) {\r\n const periodColumnId = `${filter.columnId}__period`;\r\n const periodColumn = columnsMap.get(periodColumnId);\r\n if (periodColumn) {\r\n // Map to Period pseudo-column\r\n columnId = periodColumnId;\r\n }\r\n }\r\n\r\n // Find the method object from the methods configuration\r\n if (filter.type) {\r\n const availableMethods = filterMethods[filter.type as ColumnDataType];\r\n const methodObj = availableMethods?.find((m: FilterMethod) => m.value === filter.method);\r\n if (methodObj) {\r\n method = methodObj;\r\n } else if (filter.method === \"isEmpty\" || filter.method === \"isNotEmpty\") {\r\n // Handle isEmpty/isNotEmpty even if not in the provided method list\r\n method = {\r\n value: filter.method,\r\n label: filter.method === \"isEmpty\" ? \"Is Empty\" : \"Is Not Empty\",\r\n component: null,\r\n };\r\n }\r\n }\r\n\r\n // Transform value based on type\r\n switch (filter.type) {\r\n case \"string\":\r\n value = filter.value as string;\r\n break;\r\n case \"number\":\r\n value = filter.value as number;\r\n break;\r\n case \"date\":\r\n // Use the shared helper for range conversion, or parse single dates as UTC\r\n if (filter.method === \"between\" || filter.method === \"intersect\") {\r\n // For range methods, use the helper function\r\n const range = dateFilterToRangeValue([filter], filter.columnId);\r\n value = range;\r\n } else {\r\n // Single date value: parse UTC ISO string and extract UTC calendar date\r\n if (typeof filter.value === \"string\" && filter.value) {\r\n const day = parseUTCToCalendarDate(filter.value);\r\n value = day ? (day as unknown as DateValue) : null;\r\n } else {\r\n value = null;\r\n }\r\n }\r\n break;\r\n case \"boolean\":\r\n value = filter.value as boolean;\r\n break;\r\n case \"enum\":\r\n if (filter.method === \"oneOf\" && Array.isArray(filter.value)) {\r\n // Multi-select: array of strings to SharedSelection (Set)\r\n value = new Set(filter.value) as SharedSelection;\r\n } else {\r\n // Single select: string to SharedSelection with currentKey\r\n const selection = new Set([filter.value as string]) as SharedSelection;\r\n // Set currentKey property\r\n Object.defineProperty(selection, \"currentKey\", {\r\n value: filter.value as string,\r\n writable: true,\r\n enumerable: false,\r\n configurable: true,\r\n });\r\n value = selection;\r\n }\r\n break;\r\n default:\r\n value = filter.value;\r\n }\r\n\r\n const column = columnsMap.get(columnId);\r\n return {\r\n columnId,\r\n type: filter.type,\r\n value,\r\n method,\r\n options: column?.options ?? null,\r\n endColumnId: column?.endColumnId ?? null,\r\n periodStartColumnId: column?.periodStartColumnId ?? null,\r\n periodEndColumnId: column?.periodEndColumnId ?? null,\r\n };\r\n });\r\n};\r\n\r\n/**\r\n * Transform filters from HeroUI format (HeroUIFilter[]) to backend format (QueryFilter[])\r\n * Used when applying filters to send to backend/URL\r\n */\r\nexport const transformFiltersFromHeroUI = (filters: HeroUIFilter[]): QueryFilters => {\r\n const filtersToApply: QueryFilters = [];\r\n\r\n for (const filter of filters) {\r\n let value: FilterValue | undefined;\r\n let valueTo: FilterValue | undefined;\r\n\r\n // Handle isEmpty/isNotEmpty methods - they don't need a value\r\n if (filter.method?.value === \"isEmpty\" || filter.method?.value === \"isNotEmpty\") {\r\n if (filter.columnId === \"\") continue;\r\n\r\n const result: QueryFilter = {\r\n columnId: filter.columnId,\r\n type: filter.type as ColumnDataType,\r\n method: filter.method.value,\r\n value: \"\",\r\n };\r\n filtersToApply.push(result);\r\n continue;\r\n }\r\n\r\n // Handle Period pseudo-column: map to intersect on real columns\r\n const isPeriodColumn = filter.columnId.endsWith(\"__period\");\r\n const actualColumnId =\r\n isPeriodColumn && filter.periodStartColumnId ? filter.periodStartColumnId : filter.columnId;\r\n const actualEndColumnId =\r\n isPeriodColumn && filter.periodEndColumnId ? filter.periodEndColumnId : filter.endColumnId;\r\n\r\n switch (filter.type) {\r\n case \"date\":\r\n if (\r\n filter.method?.value === \"between\" ||\r\n filter.method?.value === \"intersect\" ||\r\n isPeriodColumn\r\n ) {\r\n const range = filter.value as RangeValue<DateValue>;\r\n if (range?.start && range?.end) {\r\n value = calendarDateToUTC(range.start as unknown as CalendarDate);\r\n valueTo = calendarDateToUTC(range.end as unknown as CalendarDate);\r\n }\r\n } else {\r\n const dateValue = filter.value as unknown as CalendarDate;\r\n if (dateValue?.year) {\r\n value = calendarDateToUTC(dateValue);\r\n }\r\n }\r\n break;\r\n case \"enum\": {\r\n const selection = filter.value as SharedSelection;\r\n if (selection) {\r\n value =\r\n filter.method?.value === \"oneOf\"\r\n ? Array.from(selection).map(String)\r\n : String(selection.currentKey);\r\n }\r\n break;\r\n }\r\n default:\r\n value = filter.value;\r\n }\r\n\r\n // Skip filters without valid values\r\n if (!value || value === \"\" || filter.columnId === \"\") continue;\r\n if (filter.type === \"enum\" && Array.isArray(value) && value.length === 0) continue;\r\n\r\n // For Period columns, always use intersect method\r\n const actualMethod = isPeriodColumn ? \"intersect\" : (filter.method as FilterMethod).value;\r\n\r\n const result: QueryFilter = {\r\n columnId: actualColumnId,\r\n type: filter.type as ColumnDataType,\r\n method: actualMethod,\r\n value: value as string | number | boolean | string[],\r\n ...(valueTo && { valueTo: valueTo as string }),\r\n ...(actualMethod === \"intersect\" && actualEndColumnId\r\n ? { endColumnId: actualEndColumnId }\r\n : {}),\r\n };\r\n filtersToApply.push(result);\r\n }\r\n\r\n return filtersToApply;\r\n};\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"filterTransformers.js","names":["DateTime","CalendarDate"],"sources":["../../../../src/modules/table/filterTransformers.ts"],"sourcesContent":["import type { DateValue, RangeValue, SharedSelection } from \"@heroui/react\";\r\nimport { CalendarDate, getLocalTimeZone, today } from \"@internationalized/date\";\r\nimport type { QueryFilter, QueryFilters } from \"@m5kdev/commons/modules/schemas/query.schema\";\r\nimport type { ColumnDataType, FilterMethod } from \"@m5kdev/commons/modules/table/filter.types\";\r\nimport { DateTime } from \"luxon\";\r\n\r\nexport type FilterValue =\r\n | string\r\n | number\r\n | string[]\r\n | DateValue\r\n | RangeValue<DateValue>\r\n | boolean\r\n | SharedSelection\r\n | null;\r\n\r\nexport interface HeroUIFilter {\r\n columnId: string;\r\n type: ColumnDataType | null;\r\n value: FilterValue;\r\n method: FilterMethod | null;\r\n options?: { label: string; value: string }[] | null;\r\n endColumnId?: string | null;\r\n periodStartColumnId?: string | null;\r\n periodEndColumnId?: string | null;\r\n}\r\n\r\n/**\r\n * Convert CalendarDate to UTC ISO string at midnight UTC\r\n */\r\nexport const calendarDateToUTC = (date: CalendarDate): string => {\r\n return DateTime.utc(date.year, date.month, date.day).toISO() ?? \"\";\r\n};\r\n\r\n/**\r\n * Convert CalendarDate to end of day UTC ISO string\r\n */\r\nexport const calendarDateToEndOfDayUTC = (date: CalendarDate): string => {\r\n return DateTime.utc(date.year, date.month, date.day).endOf(\"day\").toISO() ?? \"\";\r\n};\r\n\r\n/**\r\n * Parse UTC ISO string to CalendarDate (preserves UTC date, no timezone shift)\r\n */\r\nconst parseUTCToCalendarDate = (isoString: string): CalendarDate | null => {\r\n try {\r\n const dt = DateTime.fromISO(isoString, { zone: \"utc\" });\r\n if (!dt.isValid) return null;\r\n return new CalendarDate(dt.year, dt.month, dt.day);\r\n } catch {\r\n return null;\r\n }\r\n};\r\n\r\n/**\r\n * Convert any date filter method from URL to a RangeValue for DateRangePicker\r\n * Handles: on, before, after, between, intersect\r\n * Parses UTC ISO strings as UTC to avoid timezone shifts\r\n */\r\nexport const dateFilterToRangeValue = (\r\n filters: QueryFilters | undefined,\r\n columnId: string\r\n): RangeValue<DateValue> | null => {\r\n if (!filters) return null;\r\n\r\n const filter = filters.find((f) => f.columnId === columnId);\r\n if (!filter || filter.type !== \"date\") return null;\r\n\r\n const todayDate = today(getLocalTimeZone());\r\n const epochStart = new CalendarDate(1970, 1, 1);\r\n\r\n try {\r\n switch (filter.method) {\r\n case \"on\": {\r\n // Same day range\r\n if (typeof filter.value !== \"string\" || !filter.value) return null;\r\n const day = parseUTCToCalendarDate(filter.value);\r\n if (!day) return null;\r\n return {\r\n start: day as unknown as DateValue,\r\n end: day as unknown as DateValue,\r\n } as RangeValue<DateValue>;\r\n }\r\n case \"before\": {\r\n // [epochStart, selectedDay]\r\n if (typeof filter.value !== \"string\" || !filter.value) return null;\r\n const day = parseUTCToCalendarDate(filter.value);\r\n if (!day) return null;\r\n return {\r\n start: epochStart as unknown as DateValue,\r\n end: day as unknown as DateValue,\r\n } as RangeValue<DateValue>;\r\n }\r\n case \"after\": {\r\n // [selectedDay, today]\r\n if (typeof filter.value !== \"string\" || !filter.value) return null;\r\n const day = parseUTCToCalendarDate(filter.value);\r\n if (!day) return null;\r\n return {\r\n start: day as unknown as DateValue,\r\n end: todayDate as unknown as DateValue,\r\n } as RangeValue<DateValue>;\r\n }\r\n case \"between\":\r\n case \"intersect\": {\r\n // [value, valueTo]\r\n if (typeof filter.value !== \"string\" || !filter.value || !filter.valueTo) return null;\r\n const startDate = parseUTCToCalendarDate(filter.value);\r\n const endDate = parseUTCToCalendarDate(filter.valueTo);\r\n if (!startDate || !endDate) return null;\r\n return {\r\n start: startDate as unknown as DateValue,\r\n end: endDate as unknown as DateValue,\r\n } as RangeValue<DateValue>;\r\n }\r\n default:\r\n return null;\r\n }\r\n } catch {\r\n return null;\r\n }\r\n};\r\n\r\n/**\r\n * Transform filters from backend format (QueryFilter[]) to HeroUI format (HeroUIFilter[])\r\n * Used when loading filters from URL/backend to populate HeroUI components\r\n */\r\nexport const transformFiltersToHeroUI = (\r\n filtersToTransform: QueryFilters,\r\n columnsMap: Map<\r\n string,\r\n {\r\n id: string;\r\n label: string;\r\n type?: ColumnDataType | null;\r\n options?: { label: string; value: string }[] | null;\r\n endColumnId?: string | null;\r\n periodStartColumnId?: string | null;\r\n periodEndColumnId?: string | null;\r\n }\r\n >,\r\n filterMethods: Record<ColumnDataType, FilterMethod[]>\r\n): HeroUIFilter[] => {\r\n return filtersToTransform.map((filter) => {\r\n let value: FilterValue = null;\r\n let method: FilterMethod | null = null;\r\n let columnId = filter.columnId;\r\n\r\n // Check if this intersect filter should map to Period pseudo-column\r\n if (filter.type === \"date\" && filter.method === \"intersect\" && filter.endColumnId) {\r\n const periodColumnId = `${filter.columnId}__period`;\r\n const periodColumn = columnsMap.get(periodColumnId);\r\n if (periodColumn) {\r\n // Map to Period pseudo-column\r\n columnId = periodColumnId;\r\n }\r\n }\r\n\r\n // Find the method object from the methods configuration\r\n if (filter.type) {\r\n const availableMethods = filterMethods[filter.type as ColumnDataType];\r\n const methodObj = availableMethods?.find((m: FilterMethod) => m.value === filter.method);\r\n if (methodObj) {\r\n method = methodObj;\r\n } else if (filter.method === \"isEmpty\" || filter.method === \"isNotEmpty\") {\r\n // Handle isEmpty/isNotEmpty even if not in the provided method list\r\n method = {\r\n value: filter.method,\r\n label: filter.method === \"isEmpty\" ? \"Is Empty\" : \"Is Not Empty\",\r\n component: null,\r\n };\r\n }\r\n }\r\n\r\n // Transform value based on type\r\n switch (filter.type) {\r\n case \"string\":\r\n value = filter.value as string;\r\n break;\r\n case \"number\":\r\n value = filter.value as number;\r\n break;\r\n case \"date\":\r\n // Use the shared helper for range conversion, or parse single dates as UTC\r\n if (filter.method === \"between\" || filter.method === \"intersect\") {\r\n // For range methods, use the helper function\r\n const range = dateFilterToRangeValue([filter], filter.columnId);\r\n value = range;\r\n } else {\r\n // Single date value: parse UTC ISO string and extract UTC calendar date\r\n if (typeof filter.value === \"string\" && filter.value) {\r\n const day = parseUTCToCalendarDate(filter.value);\r\n value = day ? (day as unknown as DateValue) : null;\r\n } else {\r\n value = null;\r\n }\r\n }\r\n break;\r\n case \"boolean\":\r\n value = filter.value as boolean;\r\n break;\r\n case \"enum\":\r\n if (filter.method === \"oneOf\" && Array.isArray(filter.value)) {\r\n // Multi-select: array of strings to SharedSelection (Set)\r\n value = new Set(filter.value) as SharedSelection;\r\n } else {\r\n // Single select: string to SharedSelection with currentKey\r\n const selection = new Set([filter.value as string]) as SharedSelection;\r\n // Set currentKey property\r\n Object.defineProperty(selection, \"currentKey\", {\r\n value: filter.value as string,\r\n writable: true,\r\n enumerable: false,\r\n configurable: true,\r\n });\r\n value = selection;\r\n }\r\n break;\r\n case \"jsonArray\":\r\n if (filter.method === \"oneOf\" && Array.isArray(filter.value)) {\r\n value = new Set(filter.value) as SharedSelection;\r\n } else if (Array.isArray(filter.value) && filter.value.length > 0) {\r\n const first = filter.value[0] as string;\r\n const selection = new Set([first]) as SharedSelection;\r\n Object.defineProperty(selection, \"currentKey\", {\r\n value: first,\r\n writable: true,\r\n enumerable: false,\r\n configurable: true,\r\n });\r\n value = selection;\r\n } else {\r\n value = filter.value;\r\n }\r\n break;\r\n default:\r\n value = filter.value;\r\n }\r\n\r\n const column = columnsMap.get(columnId);\r\n return {\r\n columnId,\r\n type: filter.type,\r\n value,\r\n method,\r\n options: column?.options ?? null,\r\n endColumnId: column?.endColumnId ?? null,\r\n periodStartColumnId: column?.periodStartColumnId ?? null,\r\n periodEndColumnId: column?.periodEndColumnId ?? null,\r\n };\r\n });\r\n};\r\n\r\n/**\r\n * Transform filters from HeroUI format (HeroUIFilter[]) to backend format (QueryFilter[])\r\n * Used when applying filters to send to backend/URL\r\n */\r\nexport const transformFiltersFromHeroUI = (filters: HeroUIFilter[]): QueryFilters => {\r\n const filtersToApply: QueryFilters = [];\r\n\r\n for (const filter of filters) {\r\n let value: FilterValue | undefined;\r\n let valueTo: FilterValue | undefined;\r\n\r\n // Handle isEmpty/isNotEmpty methods - they don't need a value\r\n if (filter.method?.value === \"isEmpty\" || filter.method?.value === \"isNotEmpty\") {\r\n if (filter.columnId === \"\") continue;\r\n\r\n const result: QueryFilter = {\r\n columnId: filter.columnId,\r\n type: filter.type as ColumnDataType,\r\n method: filter.method.value,\r\n value: \"\",\r\n };\r\n filtersToApply.push(result);\r\n continue;\r\n }\r\n\r\n // Handle Period pseudo-column: map to intersect on real columns\r\n const isPeriodColumn = filter.columnId.endsWith(\"__period\");\r\n const actualColumnId =\r\n isPeriodColumn && filter.periodStartColumnId ? filter.periodStartColumnId : filter.columnId;\r\n const actualEndColumnId =\r\n isPeriodColumn && filter.periodEndColumnId ? filter.periodEndColumnId : filter.endColumnId;\r\n\r\n switch (filter.type) {\r\n case \"date\":\r\n if (\r\n filter.method?.value === \"between\" ||\r\n filter.method?.value === \"intersect\" ||\r\n isPeriodColumn\r\n ) {\r\n const range = filter.value as RangeValue<DateValue>;\r\n if (range?.start && range?.end) {\r\n value = calendarDateToUTC(range.start as unknown as CalendarDate);\r\n valueTo = calendarDateToUTC(range.end as unknown as CalendarDate);\r\n }\r\n } else {\r\n const dateValue = filter.value as unknown as CalendarDate;\r\n if (dateValue?.year) {\r\n value = calendarDateToUTC(dateValue);\r\n }\r\n }\r\n break;\r\n case \"enum\": {\r\n const selection = filter.value as SharedSelection;\r\n if (selection) {\r\n value =\r\n filter.method?.value === \"oneOf\"\r\n ? Array.from(selection).map(String)\r\n : String(selection.currentKey);\r\n }\r\n break;\r\n }\r\n case \"jsonArray\": {\r\n const selection = filter.value as SharedSelection;\r\n if (selection) {\r\n value =\r\n filter.method?.value === \"oneOf\"\r\n ? Array.from(selection).map(String)\r\n : [String(selection.currentKey)];\r\n }\r\n break;\r\n }\r\n default:\r\n value = filter.value;\r\n }\r\n\r\n // Skip filters without valid values\r\n if (!value || value === \"\" || filter.columnId === \"\") continue;\r\n if (\r\n (filter.type === \"enum\" || filter.type === \"jsonArray\") &&\r\n Array.isArray(value) &&\r\n value.length === 0\r\n ) {\r\n continue;\r\n }\r\n\r\n // For Period columns, always use intersect method\r\n const actualMethod = isPeriodColumn ? \"intersect\" : (filter.method as FilterMethod).value;\r\n\r\n const result: QueryFilter = {\r\n columnId: actualColumnId,\r\n type: filter.type as ColumnDataType,\r\n method: actualMethod,\r\n value: value as string | number | boolean | string[],\r\n ...(valueTo && { valueTo: valueTo as string }),\r\n ...(actualMethod === \"intersect\" && actualEndColumnId\r\n ? { endColumnId: actualEndColumnId }\r\n : {}),\r\n };\r\n filtersToApply.push(result);\r\n }\r\n\r\n return filtersToApply;\r\n};\r\n"],"mappings":"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"}
@@ -134,6 +134,20 @@ const transformFiltersToHeroUI = (filtersToTransform, columnsMap, filterMethods)
134
134
  value = selection;
135
135
  }
136
136
  break;
137
+ case "jsonArray":
138
+ if (filter.method === "oneOf" && Array.isArray(filter.value)) value = new Set(filter.value);
139
+ else if (Array.isArray(filter.value) && filter.value.length > 0) {
140
+ const first = filter.value[0];
141
+ const selection = new Set([first]);
142
+ Object.defineProperty(selection, "currentKey", {
143
+ value: first,
144
+ writable: true,
145
+ enumerable: false,
146
+ configurable: true
147
+ });
148
+ value = selection;
149
+ } else value = filter.value;
150
+ break;
137
151
  default: value = filter.value;
138
152
  }
139
153
  const column = columnsMap.get(columnId);
@@ -190,10 +204,15 @@ const transformFiltersFromHeroUI = (filters) => {
190
204
  if (selection) value = filter.method?.value === "oneOf" ? Array.from(selection).map(String) : String(selection.currentKey);
191
205
  break;
192
206
  }
207
+ case "jsonArray": {
208
+ const selection = filter.value;
209
+ if (selection) value = filter.method?.value === "oneOf" ? Array.from(selection).map(String) : [String(selection.currentKey)];
210
+ break;
211
+ }
193
212
  default: value = filter.value;
194
213
  }
195
214
  if (!value || value === "" || filter.columnId === "") continue;
196
- if (filter.type === "enum" && Array.isArray(value) && value.length === 0) continue;
215
+ if ((filter.type === "enum" || filter.type === "jsonArray") && Array.isArray(value) && value.length === 0) continue;
197
216
  const actualMethod = isPeriodColumn ? "intersect" : filter.method.value;
198
217
  const result = {
199
218
  columnId: actualColumnId,
@@ -1 +1 @@
1
- {"version":3,"file":"filterTransformers.mjs","names":[],"sources":["../../../../src/modules/table/filterTransformers.ts"],"sourcesContent":["import type { DateValue, RangeValue, SharedSelection } from \"@heroui/react\";\r\nimport { CalendarDate, getLocalTimeZone, today } from \"@internationalized/date\";\r\nimport type { QueryFilter, QueryFilters } from \"@m5kdev/commons/modules/schemas/query.schema\";\r\nimport type { ColumnDataType, FilterMethod } from \"@m5kdev/commons/modules/table/filter.types\";\r\nimport { DateTime } from \"luxon\";\r\n\r\nexport type FilterValue =\r\n | string\r\n | number\r\n | string[]\r\n | DateValue\r\n | RangeValue<DateValue>\r\n | boolean\r\n | SharedSelection\r\n | null;\r\n\r\nexport interface HeroUIFilter {\r\n columnId: string;\r\n type: ColumnDataType | null;\r\n value: FilterValue;\r\n method: FilterMethod | null;\r\n options?: { label: string; value: string }[] | null;\r\n endColumnId?: string | null;\r\n periodStartColumnId?: string | null;\r\n periodEndColumnId?: string | null;\r\n}\r\n\r\n/**\r\n * Convert CalendarDate to UTC ISO string at midnight UTC\r\n */\r\nexport const calendarDateToUTC = (date: CalendarDate): string => {\r\n return DateTime.utc(date.year, date.month, date.day).toISO() ?? \"\";\r\n};\r\n\r\n/**\r\n * Convert CalendarDate to end of day UTC ISO string\r\n */\r\nexport const calendarDateToEndOfDayUTC = (date: CalendarDate): string => {\r\n return DateTime.utc(date.year, date.month, date.day).endOf(\"day\").toISO() ?? \"\";\r\n};\r\n\r\n/**\r\n * Parse UTC ISO string to CalendarDate (preserves UTC date, no timezone shift)\r\n */\r\nconst parseUTCToCalendarDate = (isoString: string): CalendarDate | null => {\r\n try {\r\n const dt = DateTime.fromISO(isoString, { zone: \"utc\" });\r\n if (!dt.isValid) return null;\r\n return new CalendarDate(dt.year, dt.month, dt.day);\r\n } catch {\r\n return null;\r\n }\r\n};\r\n\r\n/**\r\n * Convert any date filter method from URL to a RangeValue for DateRangePicker\r\n * Handles: on, before, after, between, intersect\r\n * Parses UTC ISO strings as UTC to avoid timezone shifts\r\n */\r\nexport const dateFilterToRangeValue = (\r\n filters: QueryFilters | undefined,\r\n columnId: string\r\n): RangeValue<DateValue> | null => {\r\n if (!filters) return null;\r\n\r\n const filter = filters.find((f) => f.columnId === columnId);\r\n if (!filter || filter.type !== \"date\") return null;\r\n\r\n const todayDate = today(getLocalTimeZone());\r\n const epochStart = new CalendarDate(1970, 1, 1);\r\n\r\n try {\r\n switch (filter.method) {\r\n case \"on\": {\r\n // Same day range\r\n if (typeof filter.value !== \"string\" || !filter.value) return null;\r\n const day = parseUTCToCalendarDate(filter.value);\r\n if (!day) return null;\r\n return {\r\n start: day as unknown as DateValue,\r\n end: day as unknown as DateValue,\r\n } as RangeValue<DateValue>;\r\n }\r\n case \"before\": {\r\n // [epochStart, selectedDay]\r\n if (typeof filter.value !== \"string\" || !filter.value) return null;\r\n const day = parseUTCToCalendarDate(filter.value);\r\n if (!day) return null;\r\n return {\r\n start: epochStart as unknown as DateValue,\r\n end: day as unknown as DateValue,\r\n } as RangeValue<DateValue>;\r\n }\r\n case \"after\": {\r\n // [selectedDay, today]\r\n if (typeof filter.value !== \"string\" || !filter.value) return null;\r\n const day = parseUTCToCalendarDate(filter.value);\r\n if (!day) return null;\r\n return {\r\n start: day as unknown as DateValue,\r\n end: todayDate as unknown as DateValue,\r\n } as RangeValue<DateValue>;\r\n }\r\n case \"between\":\r\n case \"intersect\": {\r\n // [value, valueTo]\r\n if (typeof filter.value !== \"string\" || !filter.value || !filter.valueTo) return null;\r\n const startDate = parseUTCToCalendarDate(filter.value);\r\n const endDate = parseUTCToCalendarDate(filter.valueTo);\r\n if (!startDate || !endDate) return null;\r\n return {\r\n start: startDate as unknown as DateValue,\r\n end: endDate as unknown as DateValue,\r\n } as RangeValue<DateValue>;\r\n }\r\n default:\r\n return null;\r\n }\r\n } catch {\r\n return null;\r\n }\r\n};\r\n\r\n/**\r\n * Transform filters from backend format (QueryFilter[]) to HeroUI format (HeroUIFilter[])\r\n * Used when loading filters from URL/backend to populate HeroUI components\r\n */\r\nexport const transformFiltersToHeroUI = (\r\n filtersToTransform: QueryFilters,\r\n columnsMap: Map<\r\n string,\r\n {\r\n id: string;\r\n label: string;\r\n type?: ColumnDataType | null;\r\n options?: { label: string; value: string }[] | null;\r\n endColumnId?: string | null;\r\n periodStartColumnId?: string | null;\r\n periodEndColumnId?: string | null;\r\n }\r\n >,\r\n filterMethods: Record<ColumnDataType, FilterMethod[]>\r\n): HeroUIFilter[] => {\r\n return filtersToTransform.map((filter) => {\r\n let value: FilterValue = null;\r\n let method: FilterMethod | null = null;\r\n let columnId = filter.columnId;\r\n\r\n // Check if this intersect filter should map to Period pseudo-column\r\n if (filter.type === \"date\" && filter.method === \"intersect\" && filter.endColumnId) {\r\n const periodColumnId = `${filter.columnId}__period`;\r\n const periodColumn = columnsMap.get(periodColumnId);\r\n if (periodColumn) {\r\n // Map to Period pseudo-column\r\n columnId = periodColumnId;\r\n }\r\n }\r\n\r\n // Find the method object from the methods configuration\r\n if (filter.type) {\r\n const availableMethods = filterMethods[filter.type as ColumnDataType];\r\n const methodObj = availableMethods?.find((m: FilterMethod) => m.value === filter.method);\r\n if (methodObj) {\r\n method = methodObj;\r\n } else if (filter.method === \"isEmpty\" || filter.method === \"isNotEmpty\") {\r\n // Handle isEmpty/isNotEmpty even if not in the provided method list\r\n method = {\r\n value: filter.method,\r\n label: filter.method === \"isEmpty\" ? \"Is Empty\" : \"Is Not Empty\",\r\n component: null,\r\n };\r\n }\r\n }\r\n\r\n // Transform value based on type\r\n switch (filter.type) {\r\n case \"string\":\r\n value = filter.value as string;\r\n break;\r\n case \"number\":\r\n value = filter.value as number;\r\n break;\r\n case \"date\":\r\n // Use the shared helper for range conversion, or parse single dates as UTC\r\n if (filter.method === \"between\" || filter.method === \"intersect\") {\r\n // For range methods, use the helper function\r\n const range = dateFilterToRangeValue([filter], filter.columnId);\r\n value = range;\r\n } else {\r\n // Single date value: parse UTC ISO string and extract UTC calendar date\r\n if (typeof filter.value === \"string\" && filter.value) {\r\n const day = parseUTCToCalendarDate(filter.value);\r\n value = day ? (day as unknown as DateValue) : null;\r\n } else {\r\n value = null;\r\n }\r\n }\r\n break;\r\n case \"boolean\":\r\n value = filter.value as boolean;\r\n break;\r\n case \"enum\":\r\n if (filter.method === \"oneOf\" && Array.isArray(filter.value)) {\r\n // Multi-select: array of strings to SharedSelection (Set)\r\n value = new Set(filter.value) as SharedSelection;\r\n } else {\r\n // Single select: string to SharedSelection with currentKey\r\n const selection = new Set([filter.value as string]) as SharedSelection;\r\n // Set currentKey property\r\n Object.defineProperty(selection, \"currentKey\", {\r\n value: filter.value as string,\r\n writable: true,\r\n enumerable: false,\r\n configurable: true,\r\n });\r\n value = selection;\r\n }\r\n break;\r\n default:\r\n value = filter.value;\r\n }\r\n\r\n const column = columnsMap.get(columnId);\r\n return {\r\n columnId,\r\n type: filter.type,\r\n value,\r\n method,\r\n options: column?.options ?? null,\r\n endColumnId: column?.endColumnId ?? null,\r\n periodStartColumnId: column?.periodStartColumnId ?? null,\r\n periodEndColumnId: column?.periodEndColumnId ?? null,\r\n };\r\n });\r\n};\r\n\r\n/**\r\n * Transform filters from HeroUI format (HeroUIFilter[]) to backend format (QueryFilter[])\r\n * Used when applying filters to send to backend/URL\r\n */\r\nexport const transformFiltersFromHeroUI = (filters: HeroUIFilter[]): QueryFilters => {\r\n const filtersToApply: QueryFilters = [];\r\n\r\n for (const filter of filters) {\r\n let value: FilterValue | undefined;\r\n let valueTo: FilterValue | undefined;\r\n\r\n // Handle isEmpty/isNotEmpty methods - they don't need a value\r\n if (filter.method?.value === \"isEmpty\" || filter.method?.value === \"isNotEmpty\") {\r\n if (filter.columnId === \"\") continue;\r\n\r\n const result: QueryFilter = {\r\n columnId: filter.columnId,\r\n type: filter.type as ColumnDataType,\r\n method: filter.method.value,\r\n value: \"\",\r\n };\r\n filtersToApply.push(result);\r\n continue;\r\n }\r\n\r\n // Handle Period pseudo-column: map to intersect on real columns\r\n const isPeriodColumn = filter.columnId.endsWith(\"__period\");\r\n const actualColumnId =\r\n isPeriodColumn && filter.periodStartColumnId ? filter.periodStartColumnId : filter.columnId;\r\n const actualEndColumnId =\r\n isPeriodColumn && filter.periodEndColumnId ? filter.periodEndColumnId : filter.endColumnId;\r\n\r\n switch (filter.type) {\r\n case \"date\":\r\n if (\r\n filter.method?.value === \"between\" ||\r\n filter.method?.value === \"intersect\" ||\r\n isPeriodColumn\r\n ) {\r\n const range = filter.value as RangeValue<DateValue>;\r\n if (range?.start && range?.end) {\r\n value = calendarDateToUTC(range.start as unknown as CalendarDate);\r\n valueTo = calendarDateToUTC(range.end as unknown as CalendarDate);\r\n }\r\n } else {\r\n const dateValue = filter.value as unknown as CalendarDate;\r\n if (dateValue?.year) {\r\n value = calendarDateToUTC(dateValue);\r\n }\r\n }\r\n break;\r\n case \"enum\": {\r\n const selection = filter.value as SharedSelection;\r\n if (selection) {\r\n value =\r\n filter.method?.value === \"oneOf\"\r\n ? Array.from(selection).map(String)\r\n : String(selection.currentKey);\r\n }\r\n break;\r\n }\r\n default:\r\n value = filter.value;\r\n }\r\n\r\n // Skip filters without valid values\r\n if (!value || value === \"\" || filter.columnId === \"\") continue;\r\n if (filter.type === \"enum\" && Array.isArray(value) && value.length === 0) continue;\r\n\r\n // For Period columns, always use intersect method\r\n const actualMethod = isPeriodColumn ? \"intersect\" : (filter.method as FilterMethod).value;\r\n\r\n const result: QueryFilter = {\r\n columnId: actualColumnId,\r\n type: filter.type as ColumnDataType,\r\n method: actualMethod,\r\n value: value as string | number | boolean | string[],\r\n ...(valueTo && { valueTo: valueTo as string }),\r\n ...(actualMethod === \"intersect\" && actualEndColumnId\r\n ? { endColumnId: actualEndColumnId }\r\n : {}),\r\n };\r\n filtersToApply.push(result);\r\n }\r\n\r\n return filtersToApply;\r\n};\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"filterTransformers.mjs","names":[],"sources":["../../../../src/modules/table/filterTransformers.ts"],"sourcesContent":["import type { DateValue, RangeValue, SharedSelection } from \"@heroui/react\";\r\nimport { CalendarDate, getLocalTimeZone, today } from \"@internationalized/date\";\r\nimport type { QueryFilter, QueryFilters } from \"@m5kdev/commons/modules/schemas/query.schema\";\r\nimport type { ColumnDataType, FilterMethod } from \"@m5kdev/commons/modules/table/filter.types\";\r\nimport { DateTime } from \"luxon\";\r\n\r\nexport type FilterValue =\r\n | string\r\n | number\r\n | string[]\r\n | DateValue\r\n | RangeValue<DateValue>\r\n | boolean\r\n | SharedSelection\r\n | null;\r\n\r\nexport interface HeroUIFilter {\r\n columnId: string;\r\n type: ColumnDataType | null;\r\n value: FilterValue;\r\n method: FilterMethod | null;\r\n options?: { label: string; value: string }[] | null;\r\n endColumnId?: string | null;\r\n periodStartColumnId?: string | null;\r\n periodEndColumnId?: string | null;\r\n}\r\n\r\n/**\r\n * Convert CalendarDate to UTC ISO string at midnight UTC\r\n */\r\nexport const calendarDateToUTC = (date: CalendarDate): string => {\r\n return DateTime.utc(date.year, date.month, date.day).toISO() ?? \"\";\r\n};\r\n\r\n/**\r\n * Convert CalendarDate to end of day UTC ISO string\r\n */\r\nexport const calendarDateToEndOfDayUTC = (date: CalendarDate): string => {\r\n return DateTime.utc(date.year, date.month, date.day).endOf(\"day\").toISO() ?? \"\";\r\n};\r\n\r\n/**\r\n * Parse UTC ISO string to CalendarDate (preserves UTC date, no timezone shift)\r\n */\r\nconst parseUTCToCalendarDate = (isoString: string): CalendarDate | null => {\r\n try {\r\n const dt = DateTime.fromISO(isoString, { zone: \"utc\" });\r\n if (!dt.isValid) return null;\r\n return new CalendarDate(dt.year, dt.month, dt.day);\r\n } catch {\r\n return null;\r\n }\r\n};\r\n\r\n/**\r\n * Convert any date filter method from URL to a RangeValue for DateRangePicker\r\n * Handles: on, before, after, between, intersect\r\n * Parses UTC ISO strings as UTC to avoid timezone shifts\r\n */\r\nexport const dateFilterToRangeValue = (\r\n filters: QueryFilters | undefined,\r\n columnId: string\r\n): RangeValue<DateValue> | null => {\r\n if (!filters) return null;\r\n\r\n const filter = filters.find((f) => f.columnId === columnId);\r\n if (!filter || filter.type !== \"date\") return null;\r\n\r\n const todayDate = today(getLocalTimeZone());\r\n const epochStart = new CalendarDate(1970, 1, 1);\r\n\r\n try {\r\n switch (filter.method) {\r\n case \"on\": {\r\n // Same day range\r\n if (typeof filter.value !== \"string\" || !filter.value) return null;\r\n const day = parseUTCToCalendarDate(filter.value);\r\n if (!day) return null;\r\n return {\r\n start: day as unknown as DateValue,\r\n end: day as unknown as DateValue,\r\n } as RangeValue<DateValue>;\r\n }\r\n case \"before\": {\r\n // [epochStart, selectedDay]\r\n if (typeof filter.value !== \"string\" || !filter.value) return null;\r\n const day = parseUTCToCalendarDate(filter.value);\r\n if (!day) return null;\r\n return {\r\n start: epochStart as unknown as DateValue,\r\n end: day as unknown as DateValue,\r\n } as RangeValue<DateValue>;\r\n }\r\n case \"after\": {\r\n // [selectedDay, today]\r\n if (typeof filter.value !== \"string\" || !filter.value) return null;\r\n const day = parseUTCToCalendarDate(filter.value);\r\n if (!day) return null;\r\n return {\r\n start: day as unknown as DateValue,\r\n end: todayDate as unknown as DateValue,\r\n } as RangeValue<DateValue>;\r\n }\r\n case \"between\":\r\n case \"intersect\": {\r\n // [value, valueTo]\r\n if (typeof filter.value !== \"string\" || !filter.value || !filter.valueTo) return null;\r\n const startDate = parseUTCToCalendarDate(filter.value);\r\n const endDate = parseUTCToCalendarDate(filter.valueTo);\r\n if (!startDate || !endDate) return null;\r\n return {\r\n start: startDate as unknown as DateValue,\r\n end: endDate as unknown as DateValue,\r\n } as RangeValue<DateValue>;\r\n }\r\n default:\r\n return null;\r\n }\r\n } catch {\r\n return null;\r\n }\r\n};\r\n\r\n/**\r\n * Transform filters from backend format (QueryFilter[]) to HeroUI format (HeroUIFilter[])\r\n * Used when loading filters from URL/backend to populate HeroUI components\r\n */\r\nexport const transformFiltersToHeroUI = (\r\n filtersToTransform: QueryFilters,\r\n columnsMap: Map<\r\n string,\r\n {\r\n id: string;\r\n label: string;\r\n type?: ColumnDataType | null;\r\n options?: { label: string; value: string }[] | null;\r\n endColumnId?: string | null;\r\n periodStartColumnId?: string | null;\r\n periodEndColumnId?: string | null;\r\n }\r\n >,\r\n filterMethods: Record<ColumnDataType, FilterMethod[]>\r\n): HeroUIFilter[] => {\r\n return filtersToTransform.map((filter) => {\r\n let value: FilterValue = null;\r\n let method: FilterMethod | null = null;\r\n let columnId = filter.columnId;\r\n\r\n // Check if this intersect filter should map to Period pseudo-column\r\n if (filter.type === \"date\" && filter.method === \"intersect\" && filter.endColumnId) {\r\n const periodColumnId = `${filter.columnId}__period`;\r\n const periodColumn = columnsMap.get(periodColumnId);\r\n if (periodColumn) {\r\n // Map to Period pseudo-column\r\n columnId = periodColumnId;\r\n }\r\n }\r\n\r\n // Find the method object from the methods configuration\r\n if (filter.type) {\r\n const availableMethods = filterMethods[filter.type as ColumnDataType];\r\n const methodObj = availableMethods?.find((m: FilterMethod) => m.value === filter.method);\r\n if (methodObj) {\r\n method = methodObj;\r\n } else if (filter.method === \"isEmpty\" || filter.method === \"isNotEmpty\") {\r\n // Handle isEmpty/isNotEmpty even if not in the provided method list\r\n method = {\r\n value: filter.method,\r\n label: filter.method === \"isEmpty\" ? \"Is Empty\" : \"Is Not Empty\",\r\n component: null,\r\n };\r\n }\r\n }\r\n\r\n // Transform value based on type\r\n switch (filter.type) {\r\n case \"string\":\r\n value = filter.value as string;\r\n break;\r\n case \"number\":\r\n value = filter.value as number;\r\n break;\r\n case \"date\":\r\n // Use the shared helper for range conversion, or parse single dates as UTC\r\n if (filter.method === \"between\" || filter.method === \"intersect\") {\r\n // For range methods, use the helper function\r\n const range = dateFilterToRangeValue([filter], filter.columnId);\r\n value = range;\r\n } else {\r\n // Single date value: parse UTC ISO string and extract UTC calendar date\r\n if (typeof filter.value === \"string\" && filter.value) {\r\n const day = parseUTCToCalendarDate(filter.value);\r\n value = day ? (day as unknown as DateValue) : null;\r\n } else {\r\n value = null;\r\n }\r\n }\r\n break;\r\n case \"boolean\":\r\n value = filter.value as boolean;\r\n break;\r\n case \"enum\":\r\n if (filter.method === \"oneOf\" && Array.isArray(filter.value)) {\r\n // Multi-select: array of strings to SharedSelection (Set)\r\n value = new Set(filter.value) as SharedSelection;\r\n } else {\r\n // Single select: string to SharedSelection with currentKey\r\n const selection = new Set([filter.value as string]) as SharedSelection;\r\n // Set currentKey property\r\n Object.defineProperty(selection, \"currentKey\", {\r\n value: filter.value as string,\r\n writable: true,\r\n enumerable: false,\r\n configurable: true,\r\n });\r\n value = selection;\r\n }\r\n break;\r\n case \"jsonArray\":\r\n if (filter.method === \"oneOf\" && Array.isArray(filter.value)) {\r\n value = new Set(filter.value) as SharedSelection;\r\n } else if (Array.isArray(filter.value) && filter.value.length > 0) {\r\n const first = filter.value[0] as string;\r\n const selection = new Set([first]) as SharedSelection;\r\n Object.defineProperty(selection, \"currentKey\", {\r\n value: first,\r\n writable: true,\r\n enumerable: false,\r\n configurable: true,\r\n });\r\n value = selection;\r\n } else {\r\n value = filter.value;\r\n }\r\n break;\r\n default:\r\n value = filter.value;\r\n }\r\n\r\n const column = columnsMap.get(columnId);\r\n return {\r\n columnId,\r\n type: filter.type,\r\n value,\r\n method,\r\n options: column?.options ?? null,\r\n endColumnId: column?.endColumnId ?? null,\r\n periodStartColumnId: column?.periodStartColumnId ?? null,\r\n periodEndColumnId: column?.periodEndColumnId ?? null,\r\n };\r\n });\r\n};\r\n\r\n/**\r\n * Transform filters from HeroUI format (HeroUIFilter[]) to backend format (QueryFilter[])\r\n * Used when applying filters to send to backend/URL\r\n */\r\nexport const transformFiltersFromHeroUI = (filters: HeroUIFilter[]): QueryFilters => {\r\n const filtersToApply: QueryFilters = [];\r\n\r\n for (const filter of filters) {\r\n let value: FilterValue | undefined;\r\n let valueTo: FilterValue | undefined;\r\n\r\n // Handle isEmpty/isNotEmpty methods - they don't need a value\r\n if (filter.method?.value === \"isEmpty\" || filter.method?.value === \"isNotEmpty\") {\r\n if (filter.columnId === \"\") continue;\r\n\r\n const result: QueryFilter = {\r\n columnId: filter.columnId,\r\n type: filter.type as ColumnDataType,\r\n method: filter.method.value,\r\n value: \"\",\r\n };\r\n filtersToApply.push(result);\r\n continue;\r\n }\r\n\r\n // Handle Period pseudo-column: map to intersect on real columns\r\n const isPeriodColumn = filter.columnId.endsWith(\"__period\");\r\n const actualColumnId =\r\n isPeriodColumn && filter.periodStartColumnId ? filter.periodStartColumnId : filter.columnId;\r\n const actualEndColumnId =\r\n isPeriodColumn && filter.periodEndColumnId ? filter.periodEndColumnId : filter.endColumnId;\r\n\r\n switch (filter.type) {\r\n case \"date\":\r\n if (\r\n filter.method?.value === \"between\" ||\r\n filter.method?.value === \"intersect\" ||\r\n isPeriodColumn\r\n ) {\r\n const range = filter.value as RangeValue<DateValue>;\r\n if (range?.start && range?.end) {\r\n value = calendarDateToUTC(range.start as unknown as CalendarDate);\r\n valueTo = calendarDateToUTC(range.end as unknown as CalendarDate);\r\n }\r\n } else {\r\n const dateValue = filter.value as unknown as CalendarDate;\r\n if (dateValue?.year) {\r\n value = calendarDateToUTC(dateValue);\r\n }\r\n }\r\n break;\r\n case \"enum\": {\r\n const selection = filter.value as SharedSelection;\r\n if (selection) {\r\n value =\r\n filter.method?.value === \"oneOf\"\r\n ? Array.from(selection).map(String)\r\n : String(selection.currentKey);\r\n }\r\n break;\r\n }\r\n case \"jsonArray\": {\r\n const selection = filter.value as SharedSelection;\r\n if (selection) {\r\n value =\r\n filter.method?.value === \"oneOf\"\r\n ? Array.from(selection).map(String)\r\n : [String(selection.currentKey)];\r\n }\r\n break;\r\n }\r\n default:\r\n value = filter.value;\r\n }\r\n\r\n // Skip filters without valid values\r\n if (!value || value === \"\" || filter.columnId === \"\") continue;\r\n if (\r\n (filter.type === \"enum\" || filter.type === \"jsonArray\") &&\r\n Array.isArray(value) &&\r\n value.length === 0\r\n ) {\r\n continue;\r\n }\r\n\r\n // For Period columns, always use intersect method\r\n const actualMethod = isPeriodColumn ? \"intersect\" : (filter.method as FilterMethod).value;\r\n\r\n const result: QueryFilter = {\r\n columnId: actualColumnId,\r\n type: filter.type as ColumnDataType,\r\n method: actualMethod,\r\n value: value as string | number | boolean | string[],\r\n ...(valueTo && { valueTo: valueTo as string }),\r\n ...(actualMethod === \"intersect\" && actualEndColumnId\r\n ? { endColumnId: actualEndColumnId }\r\n : {}),\r\n };\r\n filtersToApply.push(result);\r\n }\r\n\r\n return filtersToApply;\r\n};\r\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m5kdev/web-ui",
3
- "version": "0.8.9",
3
+ "version": "0.8.10",
4
4
  "license": "GPL-3.0-only",
5
5
  "repository": {
6
6
  "type": "git",
@@ -67,8 +67,8 @@
67
67
  "zod": "4.2.1",
68
68
  "@tanstack/react-query": "5.83.0",
69
69
  "@trpc/tanstack-react-query": "11.4.3",
70
- "@m5kdev/commons": "0.8.9",
71
- "@m5kdev/frontend": "0.8.9"
70
+ "@m5kdev/commons": "0.8.10",
71
+ "@m5kdev/frontend": "0.8.10"
72
72
  },
73
73
  "devDependencies": {
74
74
  "@tsdown/css": "0.21.7",
@@ -80,8 +80,8 @@
80
80
  "globals": "16.3.0",
81
81
  "tsdown": "0.21.7",
82
82
  "vite": "7.0.4",
83
- "@m5kdev/backend": "0.8.9",
84
- "@m5kdev/config": "0.8.9"
83
+ "@m5kdev/backend": "0.8.10",
84
+ "@m5kdev/config": "0.8.10"
85
85
  },
86
86
  "exports": {
87
87
  "./components/*": {