@riligar/elysia-sqlite 1.1.0 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/package.json +6 -3
  2. package/src/index.js +7 -1
  3. package/src/ui/dist/assets/_baseUniq-CtjpPx06.js +1 -0
  4. package/src/ui/dist/assets/arc-BFeA2lFC.js +1 -0
  5. package/src/ui/dist/assets/architectureDiagram-VXUJARFQ-cJh390m5.js +36 -0
  6. package/src/ui/dist/assets/blockDiagram-VD42YOAC-BhDUcRai.js +122 -0
  7. package/src/ui/dist/assets/c4Diagram-YG6GDRKO-CMTMP654.js +10 -0
  8. package/src/ui/dist/assets/channel-DVo75U_z.js +1 -0
  9. package/src/ui/dist/assets/chunk-4BX2VUAB-hf_dt5BT.js +1 -0
  10. package/src/ui/dist/assets/chunk-55IACEB6-xClE-JKq.js +1 -0
  11. package/src/ui/dist/assets/chunk-B4BG7PRW-D6pt13Ec.js +165 -0
  12. package/src/ui/dist/assets/chunk-DI55MBZ5-Dj9Qg80I.js +220 -0
  13. package/src/ui/dist/assets/chunk-FMBD7UC4-1ZlekxIE.js +15 -0
  14. package/src/ui/dist/assets/chunk-QN33PNHL-DAmen2cg.js +1 -0
  15. package/src/ui/dist/assets/chunk-QZHKN3VN-C-3XzKoW.js +1 -0
  16. package/src/ui/dist/assets/chunk-TZMSLE5B-bM4mxnvE.js +1 -0
  17. package/src/ui/dist/assets/classDiagram-2ON5EDUG-DQalC8tR.js +1 -0
  18. package/src/ui/dist/assets/classDiagram-v2-WZHVMYZB-DQalC8tR.js +1 -0
  19. package/src/ui/dist/assets/clone-VeB9KXOY.js +1 -0
  20. package/src/ui/dist/assets/cose-bilkent-S5V4N54A-BxFitB5E.js +1 -0
  21. package/src/ui/dist/assets/cytoscape.esm-BQaXIfA_.js +331 -0
  22. package/src/ui/dist/assets/dagre-6UL2VRFP-DZSkvetS.js +4 -0
  23. package/src/ui/dist/assets/defaultLocale-C4B-KCzX.js +1 -0
  24. package/src/ui/dist/assets/diagram-PSM6KHXK-CYnji4ok.js +24 -0
  25. package/src/ui/dist/assets/diagram-QEK2KX5R-BlF0GwV_.js +43 -0
  26. package/src/ui/dist/assets/diagram-S2PKOQOG-BMf9iQYN.js +24 -0
  27. package/src/ui/dist/assets/erDiagram-Q2GNP2WA-B9nG6AIS.js +60 -0
  28. package/src/ui/dist/assets/flowDiagram-NV44I4VS-cRvIpj4q.js +162 -0
  29. package/src/ui/dist/assets/ganttDiagram-JELNMOA3-D5PauD1v.js +267 -0
  30. package/src/ui/dist/assets/gitGraphDiagram-NY62KEGX-BPtCepD0.js +65 -0
  31. package/src/ui/dist/assets/graph-C9jtFm6q.js +1 -0
  32. package/src/ui/dist/assets/index-D564tWvR.js +525 -0
  33. package/src/ui/dist/assets/index-DXG21mfz.css +1 -0
  34. package/src/ui/dist/assets/infoDiagram-WHAUD3N6-0hbOLvdM.js +2 -0
  35. package/src/ui/dist/assets/init-Gi6I4Gst.js +1 -0
  36. package/src/ui/dist/assets/journeyDiagram-XKPGCS4Q-CBy5upkf.js +139 -0
  37. package/src/ui/dist/assets/kanban-definition-3W4ZIXB7-DrobDgMJ.js +89 -0
  38. package/src/ui/dist/assets/katex-Cu_Erd72.js +261 -0
  39. package/src/ui/dist/assets/layout-DM9qC5Az.js +1 -0
  40. package/src/ui/dist/assets/linear-Fky2CJQk.js +1 -0
  41. package/src/ui/dist/assets/min-Bjr2uZ4W.js +1 -0
  42. package/src/ui/dist/assets/mindmap-definition-VGOIOE7T-CIlFmeVI.js +68 -0
  43. package/src/ui/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  44. package/src/ui/dist/assets/pieDiagram-ADFJNKIX-DMIZSQUQ.js +30 -0
  45. package/src/ui/dist/assets/quadrantDiagram-AYHSOK5B-DBCuVneU.js +7 -0
  46. package/src/ui/dist/assets/requirementDiagram-UZGBJVZJ-Dc87Li5r.js +64 -0
  47. package/src/ui/dist/assets/sankeyDiagram-TZEHDZUN-SnjPjzIj.js +10 -0
  48. package/src/ui/dist/assets/sequenceDiagram-WL72ISMW-CegtRWdp.js +145 -0
  49. package/src/ui/dist/assets/stateDiagram-FKZM4ZOC-DRXLGy91.js +1 -0
  50. package/src/ui/dist/assets/stateDiagram-v2-4FDKWEC3-DfAIcXf2.js +1 -0
  51. package/src/ui/dist/assets/timeline-definition-IT6M3QCI-Dfw0Almx.js +61 -0
  52. package/src/ui/dist/assets/treemap-KMMF4GRG-HxWgHRrr.js +128 -0
  53. package/src/ui/dist/assets/xychartDiagram-PRI3JC2R-C86J7PBK.js +7 -0
  54. package/src/ui/{index.html → dist/index.html} +2 -1
  55. package/src/ui/bun.lock +0 -612
  56. package/src/ui/package.json +0 -29
  57. package/src/ui/postcss.config.cjs +0 -14
  58. package/src/ui/src/App.jsx +0 -2103
  59. package/src/ui/src/components/DataGrid.jsx +0 -122
  60. package/src/ui/src/components/EditableCell.jsx +0 -166
  61. package/src/ui/src/components/ExportButton.jsx +0 -95
  62. package/src/ui/src/components/FKPreview.jsx +0 -106
  63. package/src/ui/src/components/Filter.jsx +0 -302
  64. package/src/ui/src/components/HoldButton.jsx +0 -230
  65. package/src/ui/src/components/Login.jsx +0 -148
  66. package/src/ui/src/components/Onboarding.jsx +0 -127
  67. package/src/ui/src/components/Pagination.jsx +0 -35
  68. package/src/ui/src/components/SecuritySettings.jsx +0 -273
  69. package/src/ui/src/components/TableSelector.jsx +0 -75
  70. package/src/ui/src/hooks/useFilter.js +0 -120
  71. package/src/ui/src/index.css +0 -123
  72. package/src/ui/src/main.jsx +0 -115
  73. package/src/ui/vite.config.js +0 -19
@@ -1,127 +0,0 @@
1
- import { useState } from "react";
2
- import {
3
- Paper,
4
- Title,
5
- Text,
6
- TextInput,
7
- PasswordInput,
8
- Button,
9
- Container,
10
- Group,
11
- Stack,
12
- ThemeIcon,
13
- Box,
14
- } from "@mantine/core";
15
- import { IconDatabase, IconShieldLock } from "@tabler/icons-react";
16
- import { notifications } from "@mantine/notifications";
17
-
18
- export function Onboarding({ onConfigured }) {
19
- const [username, setUsername] = useState("admin");
20
- const [password, setPassword] = useState("");
21
- const [loading, setLoading] = useState(false);
22
-
23
- const handleSubmit = async (e) => {
24
- e.preventDefault();
25
- if (!password) {
26
- notifications.show({
27
- title: "Error",
28
- message: "Password is required",
29
- color: "red",
30
- });
31
- return;
32
- }
33
-
34
- setLoading(true);
35
- try {
36
- const response = await fetch("/admin/api/setup", {
37
- method: "POST",
38
- headers: { "Content-Type": "application/json" },
39
- body: JSON.stringify({ username, password }),
40
- });
41
- const data = await response.json();
42
-
43
- if (data.success) {
44
- notifications.show({
45
- title: "Welcome!",
46
- message: "System configured successfully",
47
- color: "green",
48
- });
49
- onConfigured();
50
- } else {
51
- notifications.show({
52
- title: "Error",
53
- message: data.error,
54
- color: "red",
55
- });
56
- }
57
- } catch (error) {
58
- notifications.show({
59
- title: "Error",
60
- message: "Failed to connect to server",
61
- color: "red",
62
- });
63
- }
64
- setLoading(false);
65
- };
66
-
67
- return (
68
- <Box
69
- style={{
70
- height: "100vh",
71
- display: "flex",
72
- alignItems: "center",
73
- justifyContent: "center",
74
- backgroundColor: "#FBFAF8",
75
- }}
76
- >
77
- <Container size={420}>
78
- <Stack align="center" mb="xl">
79
- <ThemeIcon size={60} radius="md" color="dark">
80
- <IconDatabase size={34} />
81
- </ThemeIcon>
82
- <Title order={1} fw={700}>
83
- Welcome to SQLite Admin
84
- </Title>
85
- <Text c="dimmed" size="sm" ta="center">
86
- Set up your administrator credentials to start managing your database.
87
- </Text>
88
- </Stack>
89
-
90
- <Paper withBorder shadow="md" p={30} radius="md">
91
- <form onSubmit={handleSubmit}>
92
- <Stack>
93
- <TextInput
94
- label="Administrator Username"
95
- placeholder="admin"
96
- required
97
- value={username}
98
- onChange={(e) => setUsername(e.target.value)}
99
- />
100
- <PasswordInput
101
- label="Password"
102
- placeholder="Choose a strong password"
103
- required
104
- value={password}
105
- onChange={(e) => setPassword(e.target.value)}
106
- />
107
- <Group mt="lg" justify="flex-end">
108
- <Button
109
- type="submit"
110
- color="dark"
111
- fullWidth
112
- loading={loading}
113
- leftSection={<IconShieldLock size={18} />}
114
- >
115
- Save and Start
116
- </Button>
117
- </Group>
118
- </Stack>
119
- </form>
120
- </Paper>
121
- <Text c="dimmed" size="xs" ta="center" mt="xl">
122
- This configuration will be saved in sqlite-admin-config.json
123
- </Text>
124
- </Container>
125
- </Box>
126
- );
127
- }
@@ -1,35 +0,0 @@
1
- import { Group, Text, ActionIcon } from "@mantine/core";
2
- import { IconChevronLeft, IconChevronRight } from "@tabler/icons-react";
3
-
4
- export const Pagination = ({
5
- page,
6
- total,
7
- limit = 50,
8
- onPageChange
9
- }) => {
10
- const totalPages = Math.ceil(total / limit);
11
-
12
- return (
13
- <Group justify="flex-end" gap="xs" mt="xs">
14
- <Text size="xs" c="dimmed">
15
- Page {page} of {totalPages} ({total} records)
16
- </Text>
17
- <ActionIcon
18
- variant="default"
19
- size="sm"
20
- disabled={page <= 1}
21
- onClick={() => onPageChange(page - 1)}
22
- >
23
- <IconChevronLeft size={14} />
24
- </ActionIcon>
25
- <ActionIcon
26
- variant="default"
27
- size="sm"
28
- disabled={page >= totalPages}
29
- onClick={() => onPageChange(page + 1)}
30
- >
31
- <IconChevronRight size={14} />
32
- </ActionIcon>
33
- </Group>
34
- );
35
- };
@@ -1,273 +0,0 @@
1
- import { useState, useEffect } from "react";
2
- import {
3
- Modal,
4
- Button,
5
- Text,
6
- Stack,
7
- Group,
8
- Image,
9
- TextInput,
10
- ThemeIcon,
11
- Paper,
12
- Alert,
13
- Loader,
14
- CopyButton,
15
- ActionIcon,
16
- Tooltip,
17
- } from "@mantine/core";
18
- import { notifications } from "@mantine/notifications";
19
- import {
20
- IconDeviceMobile,
21
- IconCheck,
22
- IconX,
23
- IconCopy,
24
- IconShieldLock,
25
- IconAlertCircle,
26
- } from "@tabler/icons-react";
27
-
28
- export function SecuritySettings({ opened, onClose }) {
29
- const [status, setStatus] = useState(null); // { enabled: boolean }
30
- const [loading, setLoading] = useState(true);
31
-
32
- // Setup Flow State
33
- const [setupStep, setSetupStep] = useState(0); // 0: idle, 1: generating, 2: verifying
34
- const [qrData, setQrData] = useState(null); // { secret, qrCode }
35
- const [verifyCode, setVerifyCode] = useState("");
36
- const [verifying, setVerifying] = useState(false);
37
-
38
- useEffect(() => {
39
- if (opened) loadStatus();
40
- }, [opened]);
41
-
42
- const loadStatus = async () => {
43
- setLoading(true);
44
- try {
45
- const res = await fetch("/admin/auth/status");
46
- const data = await res.json();
47
- setStatus(data);
48
- } catch (e) {
49
- notifications.show({
50
- title: "Error",
51
- message: "Failed to load status",
52
- color: "red",
53
- });
54
- }
55
- setLoading(false);
56
- };
57
-
58
- const startSetup = async () => {
59
- setSetupStep(1); // generating
60
- try {
61
- const res = await fetch("/admin/api/totp/generate", { method: "POST" });
62
- const data = await res.json();
63
- if (data.success) {
64
- setQrData(data);
65
- setSetupStep(2);
66
- } else {
67
- notifications.show({
68
- title: "Error",
69
- message: "Failed to generate QR",
70
- color: "red",
71
- });
72
- setSetupStep(0);
73
- }
74
- } catch (e) {
75
- setSetupStep(0);
76
- }
77
- };
78
-
79
- const verifyAndEnable = async () => {
80
- if (!verifyCode) return;
81
- setVerifying(true);
82
- try {
83
- const res = await fetch("/admin/api/totp/verify", {
84
- method: "POST",
85
- headers: { "Content-Type": "application/json" },
86
- body: JSON.stringify({ secret: qrData.secret, code: verifyCode }),
87
- });
88
- const data = await res.json();
89
- if (data.success) {
90
- notifications.show({
91
- title: "Success",
92
- message: "2FA Enabled!",
93
- color: "green",
94
- });
95
- setSetupStep(0);
96
- loadStatus();
97
- } else {
98
- notifications.show({
99
- title: "Error",
100
- message: "Invalid code",
101
- color: "red",
102
- });
103
- }
104
- } catch (e) {
105
- notifications.show({
106
- title: "Error",
107
- message: "Request failed",
108
- color: "red",
109
- });
110
- }
111
- setVerifying(false);
112
- };
113
-
114
- const disable2FA = async () => {
115
- // In a real app we might ask for code confirmation here too,
116
- // but for simplicity we'll assume session auth is enough or ask for code in a prompt
117
- // For this UI, let's ask for code to confirm disable
118
- const code = prompt("Enter your 2FA code to confirm disabling:");
119
- if (!code) return;
120
-
121
- try {
122
- const res = await fetch("/admin/api/totp/disable", {
123
- method: "POST",
124
- headers: { "Content-Type": "application/json" },
125
- body: JSON.stringify({ code }),
126
- });
127
- const data = await res.json();
128
- if (data.success) {
129
- notifications.show({
130
- title: "Success",
131
- message: "2FA Disabled",
132
- color: "gray",
133
- });
134
- loadStatus();
135
- } else {
136
- notifications.show({
137
- title: "Error",
138
- message: data.error || "Failed to disable",
139
- color: "red",
140
- });
141
- }
142
- } catch (e) {
143
- notifications.show({
144
- title: "Error",
145
- message: "Request failed",
146
- color: "red",
147
- });
148
- }
149
- };
150
-
151
- return (
152
- <Modal
153
- opened={opened}
154
- onClose={onClose}
155
- title="Security Settings"
156
- size="lg"
157
- >
158
- {loading ? (
159
- <Stack align="center" py="xl">
160
- <Loader />
161
- </Stack>
162
- ) : (
163
- <Stack>
164
- <Group justify="space-between" align="flex-start">
165
- <Group>
166
- <ThemeIcon
167
- size="xl"
168
- radius="md"
169
- color={status?.totpEnabled ? "green" : "gray"}
170
- variant="light"
171
- >
172
- <IconDeviceMobile size={28} />
173
- </ThemeIcon>
174
- <div>
175
- <Text fw={600}>Two-Factor Authentication (2FA)</Text>
176
- <Text size="sm" c="dimmed">
177
- {status?.totpEnabled
178
- ? "Your account is secured with 2FA."
179
- : "Add an extra layer of security to your account."}
180
- </Text>
181
- </div>
182
- </Group>
183
- {status?.totpEnabled ? (
184
- <Button color="red" variant="subtle" onClick={disable2FA}>
185
- Disable
186
- </Button>
187
- ) : (
188
- setupStep === 0 && (
189
- <Button color="dark" onClick={startSetup}>
190
- Enable 2FA
191
- </Button>
192
- )
193
- )}
194
- </Group>
195
-
196
- {/* Setup Wizard */}
197
- {setupStep === 2 && qrData && (
198
- <Paper withBorder p="md" radius="md" bg="gray.0">
199
- <Stack align="center">
200
- <Text fw={600}>1. Scan QR Code</Text>
201
- <Text size="sm" c="dimmed" ta="center">
202
- Use your authenticator app (Google Authenticator, Authy, etc.)
203
- to scan this code.
204
- </Text>
205
-
206
- <div
207
- style={{
208
- background: "white",
209
- padding: "10px",
210
- borderRadius: "8px",
211
- }}
212
- >
213
- <Image src={qrData.qrCode} w={180} h={180} />
214
- </div>
215
-
216
- <Group gap="xs">
217
- <Text size="xs" c="dimmed">
218
- Secret: {qrData.secret}
219
- </Text>
220
- <CopyButton value={qrData.secret}>
221
- {({ copied, copy }) => (
222
- <ActionIcon
223
- variant="subtle"
224
- color={copied ? "green" : "gray"}
225
- onClick={copy}
226
- size="xs"
227
- >
228
- {copied ? (
229
- <IconCheck size={12} />
230
- ) : (
231
- <IconCopy size={12} />
232
- )}
233
- </ActionIcon>
234
- )}
235
- </CopyButton>
236
- </Group>
237
-
238
- <Text fw={600} mt="md">
239
- 2. Verify Code
240
- </Text>
241
- <Group align="flex-start">
242
- <TextInput
243
- placeholder="000 000"
244
- value={verifyCode}
245
- onChange={(e) => setVerifyCode(e.target.value)}
246
- maxLength={6}
247
- w={140}
248
- />
249
- <Button
250
- onClick={verifyAndEnable}
251
- loading={verifying}
252
- color="dark"
253
- >
254
- Verify
255
- </Button>
256
- </Group>
257
-
258
- <Button
259
- variant="subtle"
260
- size="xs"
261
- color="gray"
262
- onClick={() => setSetupStep(0)}
263
- >
264
- Cancel
265
- </Button>
266
- </Stack>
267
- </Paper>
268
- )}
269
- </Stack>
270
- )}
271
- </Modal>
272
- );
273
- }
@@ -1,75 +0,0 @@
1
- import { Stack, Text, NavLink, ScrollArea } from "@mantine/core";
2
- import { IconTable } from "@tabler/icons-react";
3
-
4
- export const TableSelector = ({
5
- tables,
6
- favorites,
7
- currentTable,
8
- onSelectTable
9
- }) => {
10
- return (
11
- <ScrollArea style={{ flex: 1 }}>
12
- <Stack gap={0}>
13
- <Text
14
- size="xs"
15
- fw={500}
16
- c="#91918E"
17
- px="xs"
18
- mb={4}
19
- mt="xs"
20
- style={{
21
- textTransform: "uppercase",
22
- fontSize: "11px",
23
- letterSpacing: "0.03em",
24
- }}
25
- >
26
- Favorites
27
- </Text>
28
- {favorites.map((name) => (
29
- <NavLink
30
- key={name}
31
- label={name}
32
- leftSection={<IconTable size={16} />}
33
- active={currentTable === name}
34
- onClick={() => onSelectTable(name)}
35
- style={{ borderRadius: 6 }}
36
- />
37
- ))}
38
-
39
- {favorites.length === 0 && (
40
- <Text size="xs" c="dimmed" px="sm" py={2} fs="italic">
41
- No favorites
42
- </Text>
43
- )}
44
-
45
- <Text
46
- size="xs"
47
- fw={500}
48
- c="#91918E"
49
- px="xs"
50
- mb={4}
51
- mt="lg"
52
- style={{
53
- textTransform: "uppercase",
54
- fontSize: "11px",
55
- letterSpacing: "0.03em",
56
- }}
57
- >
58
- Tables
59
- </Text>
60
- {tables
61
- .filter((t) => !favorites.includes(t.name))
62
- .map((t) => (
63
- <NavLink
64
- key={t.name}
65
- label={t.name}
66
- leftSection={<IconTable size={16} />}
67
- active={currentTable === t.name}
68
- onClick={() => onSelectTable(t.name)}
69
- style={{ borderRadius: 6 }}
70
- />
71
- ))}
72
- </Stack>
73
- </ScrollArea>
74
- );
75
- };
@@ -1,120 +0,0 @@
1
- import { useState, useMemo } from "react";
2
-
3
- export function useFilter(items, filterKeys = []) {
4
- const [query, setQuery] = useState("");
5
- const [activeFilters, setActiveFilters] = useState({});
6
- const [logicalOperators, setLogicalOperators] = useState({}); // Stores AND/OR between filters
7
-
8
- // Helper function to apply operator
9
- const applyOperator = (elementValue, operator, filterValue) => {
10
- const elemStr = String(elementValue).toLowerCase();
11
- const filterStr = String(filterValue).toLowerCase();
12
- const elemNum = Number(elementValue);
13
- const filterNum = Number(filterValue);
14
-
15
- switch (operator) {
16
- case "=":
17
- return elemStr === filterStr;
18
- case "!=":
19
- return elemStr !== filterStr;
20
- case "contains":
21
- return elemStr.includes(filterStr);
22
- case "not_contains":
23
- return !elemStr.includes(filterStr);
24
- case ">":
25
- return !isNaN(elemNum) && !isNaN(filterNum) && elemNum > filterNum;
26
- case "<":
27
- return !isNaN(elemNum) && !isNaN(filterNum) && elemNum < filterNum;
28
- case ">=":
29
- return !isNaN(elemNum) && !isNaN(filterNum) && elemNum >= filterNum;
30
- case "<=":
31
- return !isNaN(elemNum) && !isNaN(filterNum) && elemNum <= filterNum;
32
- default:
33
- return elemStr.includes(filterStr);
34
- }
35
- };
36
-
37
- // Filter elements based on active filters + query + logical operators
38
- const filteredItems = useMemo(() => {
39
- if (!items) return [];
40
-
41
- return items.filter((element) => {
42
- // Free text filter (query)
43
- if (query.trim()) {
44
- const searchTerm = query.toLowerCase();
45
- // If filterKeys are provided, search only in those keys, otherwise search in all values
46
- const searchKeys =
47
- filterKeys.length > 0 ? filterKeys : Object.keys(element);
48
-
49
- const matchesSearch = searchKeys.some((key) => {
50
- const val = element[key];
51
- return val && String(val).toLowerCase().includes(searchTerm);
52
- });
53
-
54
- if (!matchesSearch) return false;
55
- }
56
-
57
- // Key:operator:value filters (active chips)
58
- const activeFilterKeys = Object.keys(activeFilters);
59
- if (activeFilterKeys.length === 0) return true;
60
-
61
- // Evaluate each filter
62
- const filterResults = {};
63
- for (const key of activeFilterKeys) {
64
- const valueStr = activeFilters[key];
65
- const values = valueStr.split(",").map((v) => v.trim());
66
- let matches = false;
67
-
68
- for (const value of values) {
69
- let operator = "=";
70
- let filterValue = value;
71
-
72
- const opMatch = value.match(
73
- /^([=!><]+|contains|not_contains)\s*(.*)$/
74
- );
75
- if (opMatch) {
76
- operator = opMatch[1];
77
- filterValue = opMatch[2];
78
- }
79
-
80
- const elementValue = element[key];
81
- if (
82
- elementValue !== undefined &&
83
- applyOperator(elementValue, operator, filterValue)
84
- ) {
85
- matches = true;
86
- break;
87
- }
88
- }
89
- filterResults[key] = matches;
90
- }
91
-
92
- // Apply logical operators between filters
93
- let result = filterResults[activeFilterKeys[0]];
94
- for (let i = 1; i < activeFilterKeys.length; i++) {
95
- const logicalOp = logicalOperators[activeFilterKeys[i - 1]] || "AND";
96
- if (logicalOp === "OR") {
97
- result = result || filterResults[activeFilterKeys[i]];
98
- } else {
99
- result = result && filterResults[activeFilterKeys[i]];
100
- }
101
- }
102
-
103
- return result;
104
- });
105
- }, [items, query, activeFilters, logicalOperators, filterKeys]);
106
-
107
- return {
108
- filteredItems,
109
- data: {
110
- query,
111
- activeFilters,
112
- logicalOperators,
113
- },
114
- setData: {
115
- setQuery,
116
- setActiveFilters,
117
- setLogicalOperators,
118
- },
119
- };
120
- }