ar-design 0.4.44 → 0.4.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/data-display/kanban-board/IProps.d.ts +7 -1
- package/dist/components/data-display/kanban-board/filter/index.d.ts +2 -2
- package/dist/components/data-display/kanban-board/filter/index.js +10 -2
- package/dist/components/data-display/kanban-board/index.d.ts +1 -1
- package/dist/components/data-display/kanban-board/index.js +86 -79
- package/package.json +1 -1
|
@@ -7,11 +7,16 @@ export type Config<T extends object> = {
|
|
|
7
7
|
bottom?: number;
|
|
8
8
|
left?: number;
|
|
9
9
|
};
|
|
10
|
+
perPage?: number;
|
|
10
11
|
filter?: {
|
|
12
|
+
/**
|
|
13
|
+
* @deprecated *
|
|
14
|
+
*/
|
|
11
15
|
search?: (item: T, value: string) => boolean;
|
|
12
16
|
keys: (item: T) => {
|
|
17
|
+
key: keyof T;
|
|
13
18
|
name: string;
|
|
14
|
-
|
|
19
|
+
value: string;
|
|
15
20
|
type: "select" | "date";
|
|
16
21
|
}[];
|
|
17
22
|
};
|
|
@@ -20,6 +25,7 @@ interface IProps<T extends object, TColumnProperties> {
|
|
|
20
25
|
trackBy: (item: T) => string;
|
|
21
26
|
columns: KanbanBoardColumnType<T, TColumnProperties>[];
|
|
22
27
|
onChange?: (item: T, columnKey: string, columnProperties: TColumnProperties, hoverIndex: number) => void;
|
|
28
|
+
onLazyLoad?: (query: Record<string, string>, perPage: number, currentPage: number) => void;
|
|
23
29
|
config?: Config<T>;
|
|
24
30
|
}
|
|
25
31
|
export default IProps;
|
|
@@ -4,8 +4,8 @@ import { Config } from "../IProps";
|
|
|
4
4
|
interface IProps<T extends object> {
|
|
5
5
|
states: {
|
|
6
6
|
search: {
|
|
7
|
-
get: string;
|
|
8
|
-
set: Dispatch<React.SetStateAction<string>>;
|
|
7
|
+
get: string | null;
|
|
8
|
+
set: Dispatch<React.SetStateAction<string | null>>;
|
|
9
9
|
};
|
|
10
10
|
dateFilters: {
|
|
11
11
|
get: Record<string, {
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { useState } from "react";
|
|
2
|
+
import { useRef, useState } from "react";
|
|
3
3
|
import DateFilters from "./DateFilters";
|
|
4
4
|
import SelectFilters from "./SelectFilters";
|
|
5
5
|
import React from "react";
|
|
6
6
|
import Input from "../../../form/input";
|
|
7
7
|
import { useTranslation } from "../../../../libs/core/application/hooks";
|
|
8
8
|
function Filter({ states, config }) {
|
|
9
|
+
// refs
|
|
10
|
+
const _searchTimeOut = useRef(null);
|
|
9
11
|
// states
|
|
10
12
|
const [openName, setOpenName] = useState(null);
|
|
11
13
|
// hooks
|
|
@@ -13,7 +15,13 @@ function Filter({ states, config }) {
|
|
|
13
15
|
// methods
|
|
14
16
|
const handleOpen = (name) => setOpenName(openName === name ? null : name);
|
|
15
17
|
return (React.createElement("div", { className: "filters" },
|
|
16
|
-
React.createElement(Input, { variant: "borderless", placeholder: t("KanbanBoard.Search.Input.Placeholder"), onChange: (event) =>
|
|
18
|
+
React.createElement(Input, { variant: "borderless", placeholder: t("KanbanBoard.Search.Input.Placeholder"), onChange: (event) => {
|
|
19
|
+
if (_searchTimeOut.current)
|
|
20
|
+
clearTimeout(_searchTimeOut.current);
|
|
21
|
+
_searchTimeOut.current = setTimeout(() => {
|
|
22
|
+
states.search.set(event.target.value.toLocaleLowerCase());
|
|
23
|
+
}, 750);
|
|
24
|
+
} }),
|
|
17
25
|
React.createElement("ul", null,
|
|
18
26
|
React.createElement(DateFilters, { states: {
|
|
19
27
|
dateFilters: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import IProps from "./IProps";
|
|
3
3
|
import "../../../assets/css/components/data-display/kanban-board/styles.css";
|
|
4
|
-
declare const KanbanBoard: <T extends object, TColumnProperties>({ trackBy, columns, onChange, config, }: IProps<T, TColumnProperties>) => React.JSX.Element;
|
|
4
|
+
declare const KanbanBoard: <T extends object, TColumnProperties>({ trackBy, columns, onChange, onLazyLoad, config, }: IProps<T, TColumnProperties>) => React.JSX.Element;
|
|
5
5
|
export default KanbanBoard;
|
|
@@ -1,21 +1,28 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import React, { useEffect, useRef, useState } from "react";
|
|
2
|
+
import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
3
3
|
import "../../../assets/css/components/data-display/kanban-board/styles.css";
|
|
4
4
|
import DnD from "../dnd";
|
|
5
5
|
import { ARIcon } from "../../icons";
|
|
6
6
|
import Filter from "./filter";
|
|
7
|
-
const KanbanBoard = function ({ trackBy, columns, onChange, config, }) {
|
|
7
|
+
const KanbanBoard = function ({ trackBy, columns, onChange, onLazyLoad, config, }) {
|
|
8
8
|
// refs
|
|
9
9
|
const _kanbanWrapper = useRef(null);
|
|
10
10
|
const _kanbanItems = useRef({});
|
|
11
11
|
const _hoverItemIndex = useRef(null);
|
|
12
|
+
const _isProgrammaticScroll = useRef(false);
|
|
12
13
|
const _scrollInterval = useRef(null);
|
|
13
14
|
const _scrollAnimationFrame = useRef(null);
|
|
14
|
-
const _scrollSpeedRef = useRef(0);
|
|
15
|
+
const _scrollSpeedRef = useRef(0);
|
|
16
|
+
const _lastScrollTop = useRef(0);
|
|
17
|
+
const _lastRequest = useRef("");
|
|
15
18
|
// states
|
|
16
19
|
const [data, setData] = useState([]);
|
|
20
|
+
// states -> Lazy Load
|
|
21
|
+
const [query, setQuery] = useState(null);
|
|
22
|
+
const [perPage] = useState(config?.perPage ?? 10);
|
|
23
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
17
24
|
// states -> Filters
|
|
18
|
-
const [search, setSearch] = useState(
|
|
25
|
+
const [search, setSearch] = useState(null);
|
|
19
26
|
const [selectFilters, setSelectFilters] = useState({});
|
|
20
27
|
const [selectedFilters, setSelectedFilters] = useState({});
|
|
21
28
|
const [dateFilters, setDateFilters] = useState({});
|
|
@@ -104,6 +111,22 @@ const KanbanBoard = function ({ trackBy, columns, onChange, config, }) {
|
|
|
104
111
|
if (item.classList.contains("dragging"))
|
|
105
112
|
item.classList.remove("dragging");
|
|
106
113
|
};
|
|
114
|
+
const handleLazyLoadScroll = useCallback((event) => {
|
|
115
|
+
const target = event.currentTarget;
|
|
116
|
+
const { scrollTop, scrollHeight, clientHeight } = target;
|
|
117
|
+
if (_isProgrammaticScroll.current) {
|
|
118
|
+
if (scrollTop <= 5)
|
|
119
|
+
_isProgrammaticScroll.current = false;
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const isScrollingDown = scrollTop > _lastScrollTop.current;
|
|
123
|
+
_lastScrollTop.current = scrollTop;
|
|
124
|
+
if (!isScrollingDown)
|
|
125
|
+
return;
|
|
126
|
+
const isBottom = scrollHeight - scrollTop <= clientHeight;
|
|
127
|
+
if (isBottom)
|
|
128
|
+
setCurrentPage((prev) => prev + 1);
|
|
129
|
+
}, []);
|
|
107
130
|
const handleStartScroll = (direction) => {
|
|
108
131
|
const el = _kanbanWrapper.current;
|
|
109
132
|
if (!el)
|
|
@@ -138,6 +161,11 @@ const KanbanBoard = function ({ trackBy, columns, onChange, config, }) {
|
|
|
138
161
|
};
|
|
139
162
|
// useEffects
|
|
140
163
|
useEffect(() => {
|
|
164
|
+
let _data = columns.map((col) => ({
|
|
165
|
+
...col,
|
|
166
|
+
items: [...col.items],
|
|
167
|
+
}));
|
|
168
|
+
setData(_data);
|
|
141
169
|
const selectMap = new Map();
|
|
142
170
|
const dateMap = new Map();
|
|
143
171
|
columns.forEach((col) => {
|
|
@@ -147,7 +175,7 @@ const KanbanBoard = function ({ trackBy, columns, onChange, config, }) {
|
|
|
147
175
|
if (k.type === "select") {
|
|
148
176
|
if (!selectMap.has(k.name))
|
|
149
177
|
selectMap.set(k.name, new Set());
|
|
150
|
-
selectMap.get(k.name).add(k.
|
|
178
|
+
selectMap.get(k.name).add(k.value ?? null);
|
|
151
179
|
}
|
|
152
180
|
if (k.type === "date")
|
|
153
181
|
dateMap.set(k.name, true);
|
|
@@ -165,82 +193,61 @@ const KanbanBoard = function ({ trackBy, columns, onChange, config, }) {
|
|
|
165
193
|
return next;
|
|
166
194
|
});
|
|
167
195
|
}, [columns]);
|
|
196
|
+
const _prevFilters = useRef(JSON.stringify({ search, selectedFilters, dateFilters }));
|
|
168
197
|
useEffect(() => {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (isMatch && !firstMatchedId)
|
|
181
|
-
firstMatchedId = trackBy(item);
|
|
182
|
-
return isMatch;
|
|
183
|
-
});
|
|
184
|
-
return { ...col, items: filteredItems };
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
else {
|
|
188
|
-
if (config?.filter?.search)
|
|
189
|
-
config.filter.search({}, "");
|
|
190
|
-
}
|
|
191
|
-
// Select ve Date varsa...
|
|
192
|
-
nextData = nextData.map((col) => ({
|
|
193
|
-
...col,
|
|
194
|
-
items: col.items.filter((item) => {
|
|
195
|
-
const keys = config?.filter?.keys(item) ?? [];
|
|
196
|
-
// Select (checkbox)
|
|
197
|
-
const selectOk = Object.entries(selectedFilters).every(([filterName, selectedSet]) => {
|
|
198
|
-
if (!selectedSet || selectedSet.size === 0)
|
|
199
|
-
return true;
|
|
200
|
-
const value = keys.find((k) => k.name === filterName)?.key ?? null;
|
|
201
|
-
return selectedSet.has(value);
|
|
202
|
-
});
|
|
203
|
-
if (!selectOk)
|
|
204
|
-
return false;
|
|
205
|
-
// Date (range)
|
|
206
|
-
const dateOk = Object.entries(dateFilters).every(([filterName, range]) => {
|
|
207
|
-
if (!range.from && !range.to)
|
|
208
|
-
return true;
|
|
209
|
-
const raw = keys.find((k) => k.name === filterName)?.key;
|
|
210
|
-
if (!raw)
|
|
211
|
-
return false;
|
|
212
|
-
const d = new Date(raw);
|
|
213
|
-
if (range.from && d < range.from)
|
|
214
|
-
return false;
|
|
215
|
-
if (range.to && d > range.to)
|
|
216
|
-
return false;
|
|
217
|
-
return true;
|
|
218
|
-
});
|
|
219
|
-
return dateOk;
|
|
220
|
-
}),
|
|
221
|
-
}));
|
|
222
|
-
setData(nextData);
|
|
223
|
-
// Focus / Scroll Control
|
|
224
|
-
if (search?.trim() && firstMatchedId) {
|
|
225
|
-
const element = _kanbanItems.current[firstMatchedId];
|
|
226
|
-
const container = _kanbanWrapper.current;
|
|
227
|
-
if (element && container) {
|
|
228
|
-
const elementRect = element.getBoundingClientRect();
|
|
229
|
-
const containerRect = container.getBoundingClientRect();
|
|
230
|
-
// Senin hesaplaman: Mevcut scroll + elemanın container'a göre konumu - ofset değerlerin
|
|
231
|
-
const scrollLeft = container.scrollLeft + (elementRect.left - containerRect.left) - 17.5;
|
|
232
|
-
const scrollTop = container.scrollTop + (elementRect.top - containerRect.top) - 100;
|
|
233
|
-
container.scrollTo({
|
|
234
|
-
left: scrollLeft,
|
|
235
|
-
top: scrollTop,
|
|
236
|
-
behavior: "smooth",
|
|
237
|
-
});
|
|
198
|
+
const currentFilters = JSON.stringify({
|
|
199
|
+
search,
|
|
200
|
+
selectedFilters: Object.fromEntries(Object.entries(selectedFilters).map(([k, v]) => [k, Array.from(v)])),
|
|
201
|
+
dateFilters,
|
|
202
|
+
});
|
|
203
|
+
if (_prevFilters.current !== currentFilters) {
|
|
204
|
+
setCurrentPage(1);
|
|
205
|
+
_prevFilters.current = currentFilters;
|
|
206
|
+
if (_kanbanWrapper.current) {
|
|
207
|
+
_isProgrammaticScroll.current = true;
|
|
208
|
+
_kanbanWrapper.current.scrollTo({ top: 0, left: 0, behavior: "smooth" });
|
|
238
209
|
}
|
|
239
210
|
}
|
|
240
|
-
|
|
241
|
-
|
|
211
|
+
}, [search, selectedFilters, dateFilters]);
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
if (!search && Object.keys(selectedFilters).length === 0 && Object.keys(dateFilters).length === 0) {
|
|
214
|
+
setQuery(null);
|
|
215
|
+
return;
|
|
242
216
|
}
|
|
243
|
-
|
|
217
|
+
const sampleItem = columns[0]?.items[0];
|
|
218
|
+
const keys = sampleItem ? (config?.filter?.keys(sampleItem) ?? []) : [];
|
|
219
|
+
const dateQuery = Object.entries(dateFilters).reduce((acc, [name, range]) => {
|
|
220
|
+
if (range.from || range.to) {
|
|
221
|
+
const technicalKey = keys.find((k) => k.name === name)?.key || name;
|
|
222
|
+
acc[technicalKey] = {
|
|
223
|
+
from: range.from,
|
|
224
|
+
to: range.to,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
return acc;
|
|
228
|
+
}, {});
|
|
229
|
+
const selectQuery = Object.entries(selectedFilters).reduce((acc, [filterName, selectedSet]) => {
|
|
230
|
+
if (selectedSet && selectedSet.size > 0) {
|
|
231
|
+
const technicalKey = keys.find((k) => k.name === filterName)?.key || filterName;
|
|
232
|
+
acc[technicalKey] = Array.from(selectedSet);
|
|
233
|
+
}
|
|
234
|
+
return acc;
|
|
235
|
+
}, {});
|
|
236
|
+
setQuery({
|
|
237
|
+
keyword: search ?? "",
|
|
238
|
+
...dateQuery,
|
|
239
|
+
...selectQuery,
|
|
240
|
+
});
|
|
241
|
+
}, [search, selectedFilters, dateFilters]);
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
if (!onLazyLoad)
|
|
244
|
+
return;
|
|
245
|
+
const key = JSON.stringify({ query, currentPage, perPage });
|
|
246
|
+
if (_lastRequest.current === key)
|
|
247
|
+
return;
|
|
248
|
+
_lastRequest.current = key;
|
|
249
|
+
onLazyLoad(query, perPage, currentPage);
|
|
250
|
+
}, [query, currentPage, perPage, onLazyLoad]);
|
|
244
251
|
return (React.createElement(React.Fragment, null,
|
|
245
252
|
config?.filter && (React.createElement(Filter, { states: {
|
|
246
253
|
search: {
|
|
@@ -262,7 +269,7 @@ const KanbanBoard = function ({ trackBy, columns, onChange, config, }) {
|
|
|
262
269
|
}, config: config })),
|
|
263
270
|
React.createElement("div", { ref: _kanbanWrapper, className: "ar-kanban-board", style: {
|
|
264
271
|
height: `calc(100dvh - (${_kanbanWrapper.current?.getBoundingClientRect().top}px + ${config?.safeAreaOffset?.bottom ?? 0}px))`,
|
|
265
|
-
}, onDragOver: handleBoardDragOver, onDragEnd: stopScrolling, onDrop: stopScrolling },
|
|
272
|
+
}, onScroll: handleLazyLoadScroll, onDragOver: handleBoardDragOver, onDragEnd: stopScrolling, onDrop: stopScrolling },
|
|
266
273
|
React.createElement("div", { className: "buttons" },
|
|
267
274
|
React.createElement("div", { className: "button left", onMouseDown: () => handleStartScroll("left"), onMouseUp: handleStopScroll, onMouseLeave: handleStopScroll },
|
|
268
275
|
React.createElement(ARIcon, { icon: "ArrowLeft" })),
|
|
@@ -288,7 +295,7 @@ const KanbanBoard = function ({ trackBy, columns, onChange, config, }) {
|
|
|
288
295
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
289
296
|
const mouseY = event.clientY;
|
|
290
297
|
const isBelow = mouseY > rect.top + rect.height / 2;
|
|
291
|
-
_hoverItemIndex.current = isBelow ?
|
|
298
|
+
_hoverItemIndex.current = isBelow ? dndIndex + 1 : dndIndex;
|
|
292
299
|
} }, board.renderItem(item, index)));
|
|
293
300
|
}, columnKey: board.key, confing: { isMoveIcon: false } })))))))));
|
|
294
301
|
};
|