ar-design 0.4.45 → 0.4.47
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 +6 -2
- 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 +89 -95
- package/package.json +1 -1
|
@@ -9,10 +9,14 @@ export type Config<T extends object> = {
|
|
|
9
9
|
};
|
|
10
10
|
perPage?: number;
|
|
11
11
|
filter?: {
|
|
12
|
+
/**
|
|
13
|
+
* @deprecated *
|
|
14
|
+
*/
|
|
12
15
|
search?: (item: T, value: string) => boolean;
|
|
13
16
|
keys: (item: T) => {
|
|
17
|
+
key: keyof T;
|
|
14
18
|
name: string;
|
|
15
|
-
|
|
19
|
+
value: string;
|
|
16
20
|
type: "select" | "date";
|
|
17
21
|
}[];
|
|
18
22
|
};
|
|
@@ -21,7 +25,7 @@ interface IProps<T extends object, TColumnProperties> {
|
|
|
21
25
|
trackBy: (item: T) => string;
|
|
22
26
|
columns: KanbanBoardColumnType<T, TColumnProperties>[];
|
|
23
27
|
onChange?: (item: T, columnKey: string, columnProperties: TColumnProperties, hoverIndex: number) => void;
|
|
24
|
-
|
|
28
|
+
onLazyLoad?: (query: Record<string, string>, perPage: number, currentPage: number) => void;
|
|
25
29
|
config?: Config<T>;
|
|
26
30
|
}
|
|
27
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,
|
|
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,23 +1,28 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import 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,
|
|
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
15
|
const _scrollSpeedRef = useRef(0);
|
|
15
16
|
const _lastScrollTop = useRef(0);
|
|
17
|
+
const _lastRequest = useRef("");
|
|
16
18
|
// states
|
|
17
19
|
const [data, setData] = useState([]);
|
|
18
|
-
|
|
20
|
+
// states -> Lazy Load
|
|
21
|
+
const [query, setQuery] = useState(null);
|
|
22
|
+
const [perPage] = useState(config?.perPage ?? 10);
|
|
23
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
19
24
|
// states -> Filters
|
|
20
|
-
const [search, setSearch] = useState(
|
|
25
|
+
const [search, setSearch] = useState(null);
|
|
21
26
|
const [selectFilters, setSelectFilters] = useState({});
|
|
22
27
|
const [selectedFilters, setSelectedFilters] = useState({});
|
|
23
28
|
const [dateFilters, setDateFilters] = useState({});
|
|
@@ -106,20 +111,22 @@ const KanbanBoard = function ({ trackBy, columns, onChange, onLazy, config, }) {
|
|
|
106
111
|
if (item.classList.contains("dragging"))
|
|
107
112
|
item.classList.remove("dragging");
|
|
108
113
|
};
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
+
}, []);
|
|
123
130
|
const handleStartScroll = (direction) => {
|
|
124
131
|
const el = _kanbanWrapper.current;
|
|
125
132
|
if (!el)
|
|
@@ -154,6 +161,11 @@ const KanbanBoard = function ({ trackBy, columns, onChange, onLazy, config, }) {
|
|
|
154
161
|
};
|
|
155
162
|
// useEffects
|
|
156
163
|
useEffect(() => {
|
|
164
|
+
let _data = columns.map((col) => ({
|
|
165
|
+
...col,
|
|
166
|
+
items: [...col.items],
|
|
167
|
+
}));
|
|
168
|
+
setData(_data);
|
|
157
169
|
const selectMap = new Map();
|
|
158
170
|
const dateMap = new Map();
|
|
159
171
|
columns.forEach((col) => {
|
|
@@ -163,7 +175,7 @@ const KanbanBoard = function ({ trackBy, columns, onChange, onLazy, config, }) {
|
|
|
163
175
|
if (k.type === "select") {
|
|
164
176
|
if (!selectMap.has(k.name))
|
|
165
177
|
selectMap.set(k.name, new Set());
|
|
166
|
-
selectMap.get(k.name).add(k.
|
|
178
|
+
selectMap.get(k.name).add(k.value ?? null);
|
|
167
179
|
}
|
|
168
180
|
if (k.type === "date")
|
|
169
181
|
dateMap.set(k.name, true);
|
|
@@ -181,86 +193,68 @@ const KanbanBoard = function ({ trackBy, columns, onChange, onLazy, config, }) {
|
|
|
181
193
|
return next;
|
|
182
194
|
});
|
|
183
195
|
}, [columns]);
|
|
196
|
+
const _prevFilters = useRef(JSON.stringify({ search, selectedFilters, dateFilters }));
|
|
184
197
|
useEffect(() => {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
else {
|
|
204
|
-
if (config?.filter?.search)
|
|
205
|
-
config.filter.search({}, "");
|
|
206
|
-
}
|
|
207
|
-
// Select ve Date varsa...
|
|
208
|
-
nextData = nextData.map((col) => ({
|
|
209
|
-
...col,
|
|
210
|
-
items: col.items.filter((item) => {
|
|
211
|
-
const keys = config?.filter?.keys(item) ?? [];
|
|
212
|
-
// Select (checkbox)
|
|
213
|
-
const selectOk = Object.entries(selectedFilters).every(([filterName, selectedSet]) => {
|
|
214
|
-
if (!selectedSet || selectedSet.size === 0)
|
|
215
|
-
return true;
|
|
216
|
-
const value = keys.find((k) => k.name === filterName)?.key ?? null;
|
|
217
|
-
return selectedSet.has(value);
|
|
218
|
-
});
|
|
219
|
-
if (!selectOk)
|
|
220
|
-
return false;
|
|
221
|
-
// Date (range)
|
|
222
|
-
const dateOk = Object.entries(dateFilters).every(([filterName, range]) => {
|
|
223
|
-
if (!range.from && !range.to)
|
|
224
|
-
return true;
|
|
225
|
-
const raw = keys.find((k) => k.name === filterName)?.key;
|
|
226
|
-
if (!raw)
|
|
227
|
-
return false;
|
|
228
|
-
const d = new Date(raw);
|
|
229
|
-
if (range.from && d < range.from)
|
|
230
|
-
return false;
|
|
231
|
-
if (range.to && d > range.to)
|
|
232
|
-
return false;
|
|
233
|
-
return true;
|
|
234
|
-
});
|
|
235
|
-
return dateOk;
|
|
236
|
-
}),
|
|
237
|
-
}));
|
|
238
|
-
setData(nextData);
|
|
239
|
-
// Focus / Scroll Control
|
|
240
|
-
if (search?.trim() && firstMatchedId) {
|
|
241
|
-
const element = _kanbanItems.current[firstMatchedId];
|
|
242
|
-
const container = _kanbanWrapper.current;
|
|
243
|
-
if (element && container) {
|
|
244
|
-
const elementRect = element.getBoundingClientRect();
|
|
245
|
-
const containerRect = container.getBoundingClientRect();
|
|
246
|
-
// Senin hesaplaman: Mevcut scroll + elemanın container'a göre konumu - ofset değerlerin
|
|
247
|
-
const scrollLeft = container.scrollLeft + (elementRect.left - containerRect.left) - 17.5;
|
|
248
|
-
const scrollTop = container.scrollTop + (elementRect.top - containerRect.top) - 100;
|
|
249
|
-
container.scrollTo({
|
|
250
|
-
left: scrollLeft,
|
|
251
|
-
top: scrollTop,
|
|
198
|
+
const normalizedFilters = JSON.stringify({
|
|
199
|
+
search,
|
|
200
|
+
selectedFilters: Object.fromEntries(Object.entries(selectedFilters).map(([k, v]) => [k, Array.from(v).sort()])),
|
|
201
|
+
dateFilters,
|
|
202
|
+
});
|
|
203
|
+
const hasSelectedFilters = Object.values(selectedFilters).some((set) => set.size > 0);
|
|
204
|
+
const hasDateFilters = Object.values(dateFilters).some((r) => r.from || r.to);
|
|
205
|
+
// Page reset + Scroll logic.
|
|
206
|
+
if (_prevFilters.current !== normalizedFilters) {
|
|
207
|
+
setCurrentPage(1);
|
|
208
|
+
_prevFilters.current = normalizedFilters;
|
|
209
|
+
if (_kanbanWrapper.current) {
|
|
210
|
+
_isProgrammaticScroll.current = true;
|
|
211
|
+
_kanbanWrapper.current.scrollTo({
|
|
212
|
+
top: 0,
|
|
213
|
+
left: 0,
|
|
252
214
|
behavior: "smooth",
|
|
253
215
|
});
|
|
254
216
|
}
|
|
255
217
|
}
|
|
256
|
-
//
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
218
|
+
// Query Build Logic.
|
|
219
|
+
if (!search && !hasSelectedFilters && !hasDateFilters) {
|
|
220
|
+
setQuery(null);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const sampleItem = columns?.[0]?.items?.[0];
|
|
224
|
+
const keys = config?.filter?.keys(sampleItem) ?? [];
|
|
225
|
+
const keyMap = Object.fromEntries(keys.map((k) => [k.name, k.key]));
|
|
226
|
+
const dateQuery = Object.entries(dateFilters).reduce((acc, [name, range]) => {
|
|
227
|
+
if (range.from || range.to) {
|
|
228
|
+
const technicalKey = keyMap[name] || name;
|
|
229
|
+
acc[technicalKey] = {
|
|
230
|
+
from: range.from,
|
|
231
|
+
to: range.to,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return acc;
|
|
235
|
+
}, {});
|
|
236
|
+
const selectQuery = Object.entries(selectedFilters).reduce((acc, [name, set]) => {
|
|
237
|
+
if (set.size > 0) {
|
|
238
|
+
const technicalKey = keyMap[name] || name;
|
|
239
|
+
acc[technicalKey] = Array.from(set);
|
|
240
|
+
}
|
|
241
|
+
return acc;
|
|
242
|
+
}, {});
|
|
243
|
+
setQuery({
|
|
244
|
+
keyword: search ?? "",
|
|
245
|
+
...dateQuery,
|
|
246
|
+
...selectQuery,
|
|
247
|
+
});
|
|
248
|
+
}, [search, selectedFilters, dateFilters, columns, config]);
|
|
260
249
|
useEffect(() => {
|
|
261
|
-
if (
|
|
262
|
-
|
|
263
|
-
|
|
250
|
+
if (!onLazyLoad)
|
|
251
|
+
return;
|
|
252
|
+
const key = JSON.stringify({ query, currentPage, perPage });
|
|
253
|
+
if (_lastRequest.current === key)
|
|
254
|
+
return;
|
|
255
|
+
_lastRequest.current = key;
|
|
256
|
+
onLazyLoad(query, perPage, currentPage);
|
|
257
|
+
}, [query, currentPage, perPage, onLazyLoad]);
|
|
264
258
|
return (React.createElement(React.Fragment, null,
|
|
265
259
|
config?.filter && (React.createElement(Filter, { states: {
|
|
266
260
|
search: {
|
|
@@ -282,7 +276,7 @@ const KanbanBoard = function ({ trackBy, columns, onChange, onLazy, config, }) {
|
|
|
282
276
|
}, config: config })),
|
|
283
277
|
React.createElement("div", { ref: _kanbanWrapper, className: "ar-kanban-board", style: {
|
|
284
278
|
height: `calc(100dvh - (${_kanbanWrapper.current?.getBoundingClientRect().top}px + ${config?.safeAreaOffset?.bottom ?? 0}px))`,
|
|
285
|
-
}, onScroll:
|
|
279
|
+
}, onScroll: handleLazyLoadScroll, onDragOver: handleBoardDragOver, onDragEnd: stopScrolling, onDrop: stopScrolling },
|
|
286
280
|
React.createElement("div", { className: "buttons" },
|
|
287
281
|
React.createElement("div", { className: "button left", onMouseDown: () => handleStartScroll("left"), onMouseUp: handleStopScroll, onMouseLeave: handleStopScroll },
|
|
288
282
|
React.createElement(ARIcon, { icon: "ArrowLeft" })),
|
|
@@ -308,7 +302,7 @@ const KanbanBoard = function ({ trackBy, columns, onChange, onLazy, config, }) {
|
|
|
308
302
|
const rect = event.currentTarget.getBoundingClientRect();
|
|
309
303
|
const mouseY = event.clientY;
|
|
310
304
|
const isBelow = mouseY > rect.top + rect.height / 2;
|
|
311
|
-
_hoverItemIndex.current = isBelow ?
|
|
305
|
+
_hoverItemIndex.current = isBelow ? dndIndex + 1 : dndIndex;
|
|
312
306
|
} }, board.renderItem(item, index)));
|
|
313
307
|
}, columnKey: board.key, confing: { isMoveIcon: false } })))))))));
|
|
314
308
|
};
|