@industry-theme/backlogmd-kanban-panel 1.0.3 → 1.0.4
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.
|
@@ -7,17 +7,22 @@ export interface ColumnState {
|
|
|
7
7
|
hasMore: boolean;
|
|
8
8
|
isLoadingMore: boolean;
|
|
9
9
|
}
|
|
10
|
+
/** Source column names (directory-based) */
|
|
11
|
+
export type SourceColumn = 'tasks' | 'completed';
|
|
12
|
+
/** Display labels for source columns */
|
|
13
|
+
export declare const SOURCE_DISPLAY_LABELS: Record<SourceColumn, string>;
|
|
10
14
|
export interface UseKanbanDataResult {
|
|
11
15
|
tasks: Task[];
|
|
12
|
-
|
|
16
|
+
/** Source columns: "tasks" and "completed" */
|
|
17
|
+
sources: SourceColumn[];
|
|
13
18
|
isLoading: boolean;
|
|
14
19
|
error: string | null;
|
|
15
20
|
isBacklogProject: boolean;
|
|
16
|
-
|
|
21
|
+
tasksBySource: Map<string, Task[]>;
|
|
17
22
|
/** Per-column pagination state */
|
|
18
23
|
columnStates: Map<string, ColumnState>;
|
|
19
|
-
/** Load more tasks for a specific
|
|
20
|
-
loadMore: (
|
|
24
|
+
/** Load more tasks for a specific source column */
|
|
25
|
+
loadMore: (source: SourceColumn) => Promise<void>;
|
|
21
26
|
refreshData: () => Promise<void>;
|
|
22
27
|
updateTaskStatus: (taskId: string, newStatus: string) => Promise<void>;
|
|
23
28
|
}
|
|
@@ -28,8 +33,10 @@ interface UseKanbanDataOptions {
|
|
|
28
33
|
pageSize?: number;
|
|
29
34
|
}
|
|
30
35
|
/**
|
|
31
|
-
* Hook for managing kanban board data with
|
|
32
|
-
*
|
|
36
|
+
* Hook for managing kanban board data with lazy loading
|
|
37
|
+
*
|
|
38
|
+
* Uses 2-column view (Active/Completed) based on directory structure.
|
|
39
|
+
* Only loads task content for displayed items (no file reads on init).
|
|
33
40
|
*/
|
|
34
41
|
export declare function useKanbanData(options?: UseKanbanDataOptions): UseKanbanDataResult;
|
|
35
42
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useKanbanData.d.ts","sourceRoot":"","sources":["../../../../src/panels/kanban/hooks/useKanbanData.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,KAAK,IAAI,EAAwB,MAAM,kBAAkB,CAAC;AAEzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEtE,kCAAkC;AAClC,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,
|
|
1
|
+
{"version":3,"file":"useKanbanData.d.ts","sourceRoot":"","sources":["../../../../src/panels/kanban/hooks/useKanbanData.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,KAAK,IAAI,EAAwB,MAAM,kBAAkB,CAAC;AAEzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEtE,kCAAkC;AAClC,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,4CAA4C;AAC5C,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,WAAW,CAAC;AAEjD,wCAAwC;AACxC,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAG9D,CAAC;AAEF,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,8CAA8C;IAC9C,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IACnC,kCAAkC;IAClC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACvC,mDAAmD;IACnD,QAAQ,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACxE;AAED,UAAU,oBAAoB;IAC5B,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAKD;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,OAAO,CAAC,EAAE,oBAAoB,GAC7B,mBAAmB,CA4TrB"}
|
package/dist/panels.bundle.js
CHANGED
|
@@ -526,10 +526,32 @@ function extractIdFromPath(filePath) {
|
|
|
526
526
|
}
|
|
527
527
|
return filename.replace(/\.md$/, "");
|
|
528
528
|
}
|
|
529
|
+
function extractTitleFromPath(filePath) {
|
|
530
|
+
const filename = filePath.split("/").pop() || "";
|
|
531
|
+
const match = filename.match(/^(?:task-)?\d+(?:\.\d+)?\s*-\s*(.+)\.md$/);
|
|
532
|
+
if (match) {
|
|
533
|
+
return match[1].trim();
|
|
534
|
+
}
|
|
535
|
+
return filename.replace(/\.md$/, "");
|
|
536
|
+
}
|
|
537
|
+
function extractSourceFromPath(filePath) {
|
|
538
|
+
if (filePath.includes("/completed/") || filePath.includes("\\completed\\")) {
|
|
539
|
+
return "completed";
|
|
540
|
+
}
|
|
541
|
+
return "tasks";
|
|
542
|
+
}
|
|
543
|
+
function extractTaskIndexFromPath(filePath) {
|
|
544
|
+
return {
|
|
545
|
+
id: extractIdFromPath(filePath),
|
|
546
|
+
filePath,
|
|
547
|
+
title: extractTitleFromPath(filePath),
|
|
548
|
+
source: extractSourceFromPath(filePath)
|
|
549
|
+
};
|
|
550
|
+
}
|
|
529
551
|
function escapeRegex(str) {
|
|
530
552
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
531
553
|
}
|
|
532
|
-
const DEFAULT_STATUSES
|
|
554
|
+
const DEFAULT_STATUSES = ["To Do", "In Progress", "Done"];
|
|
533
555
|
function parseBacklogConfig(content) {
|
|
534
556
|
const config = {};
|
|
535
557
|
const lines = content.split("\n");
|
|
@@ -609,10 +631,10 @@ function parseBacklogConfig(content) {
|
|
|
609
631
|
}
|
|
610
632
|
return {
|
|
611
633
|
projectName: config.projectName || "Backlog",
|
|
612
|
-
statuses: config.statuses || [...DEFAULT_STATUSES
|
|
634
|
+
statuses: config.statuses || [...DEFAULT_STATUSES],
|
|
613
635
|
labels: config.labels || [],
|
|
614
636
|
milestones: config.milestones || [],
|
|
615
|
-
defaultStatus: config.defaultStatus || DEFAULT_STATUSES
|
|
637
|
+
defaultStatus: config.defaultStatus || DEFAULT_STATUSES[0],
|
|
616
638
|
dateFormat: config.dateFormat || "YYYY-MM-DD",
|
|
617
639
|
defaultAssignee: config.defaultAssignee,
|
|
618
640
|
defaultReporter: config.defaultReporter,
|
|
@@ -747,6 +769,9 @@ class Core {
|
|
|
747
769
|
__publicField(this, "config", null);
|
|
748
770
|
__publicField(this, "tasks", /* @__PURE__ */ new Map());
|
|
749
771
|
__publicField(this, "initialized", false);
|
|
772
|
+
/** Lightweight task index for lazy loading (no file reads) */
|
|
773
|
+
__publicField(this, "taskIndex", /* @__PURE__ */ new Map());
|
|
774
|
+
__publicField(this, "lazyInitialized", false);
|
|
750
775
|
this.projectRoot = options.projectRoot;
|
|
751
776
|
this.fs = options.adapters.fs;
|
|
752
777
|
}
|
|
@@ -828,6 +853,173 @@ class Core {
|
|
|
828
853
|
}
|
|
829
854
|
this.initialized = true;
|
|
830
855
|
}
|
|
856
|
+
/**
|
|
857
|
+
* Initialize with lazy loading (no file content reads)
|
|
858
|
+
*
|
|
859
|
+
* Only loads config and builds task index from file paths.
|
|
860
|
+
* Task content is loaded on-demand via loadTask().
|
|
861
|
+
* Use this for web/panel contexts where file reads are expensive.
|
|
862
|
+
*
|
|
863
|
+
* @param filePaths - Array of all file paths in the project
|
|
864
|
+
*/
|
|
865
|
+
async initializeLazy(filePaths) {
|
|
866
|
+
if (this.lazyInitialized)
|
|
867
|
+
return;
|
|
868
|
+
const configPath = this.fs.join(this.projectRoot, "backlog", "config.yml");
|
|
869
|
+
const configExists = await this.fs.exists(configPath);
|
|
870
|
+
if (!configExists) {
|
|
871
|
+
throw new Error(`Not a Backlog.md project: config.yml not found at ${configPath}`);
|
|
872
|
+
}
|
|
873
|
+
const configContent = await this.fs.readFile(configPath);
|
|
874
|
+
this.config = parseBacklogConfig(configContent);
|
|
875
|
+
this.taskIndex.clear();
|
|
876
|
+
for (const filePath of filePaths) {
|
|
877
|
+
if (!filePath.endsWith(".md"))
|
|
878
|
+
continue;
|
|
879
|
+
const isTaskFile = filePath.includes("backlog/tasks/") || filePath.includes("backlog\\tasks\\");
|
|
880
|
+
const isCompletedFile = filePath.includes("backlog/completed/") || filePath.includes("backlog\\completed\\");
|
|
881
|
+
if (!isTaskFile && !isCompletedFile)
|
|
882
|
+
continue;
|
|
883
|
+
if (filePath.endsWith("config.yml"))
|
|
884
|
+
continue;
|
|
885
|
+
const indexEntry = extractTaskIndexFromPath(filePath);
|
|
886
|
+
this.taskIndex.set(indexEntry.id, indexEntry);
|
|
887
|
+
}
|
|
888
|
+
this.lazyInitialized = true;
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Check if lazy initialization is complete
|
|
892
|
+
*/
|
|
893
|
+
isLazyInitialized() {
|
|
894
|
+
return this.lazyInitialized;
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* Get the task index (lightweight entries)
|
|
898
|
+
*/
|
|
899
|
+
getTaskIndex() {
|
|
900
|
+
if (!this.lazyInitialized) {
|
|
901
|
+
throw new Error("Core not lazy initialized. Call initializeLazy() first.");
|
|
902
|
+
}
|
|
903
|
+
return this.taskIndex;
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* Load a single task by ID (on-demand loading)
|
|
907
|
+
*
|
|
908
|
+
* @param id - Task ID
|
|
909
|
+
* @returns Task or undefined if not found
|
|
910
|
+
*/
|
|
911
|
+
async loadTask(id) {
|
|
912
|
+
if (this.tasks.has(id)) {
|
|
913
|
+
return this.tasks.get(id);
|
|
914
|
+
}
|
|
915
|
+
const indexEntry = this.taskIndex.get(id);
|
|
916
|
+
if (!indexEntry) {
|
|
917
|
+
return void 0;
|
|
918
|
+
}
|
|
919
|
+
try {
|
|
920
|
+
const content = await this.fs.readFile(indexEntry.filePath);
|
|
921
|
+
const task = parseTaskMarkdown(content, indexEntry.filePath);
|
|
922
|
+
task.source = indexEntry.source === "completed" ? "completed" : "local";
|
|
923
|
+
this.tasks.set(task.id, task);
|
|
924
|
+
return task;
|
|
925
|
+
} catch (error) {
|
|
926
|
+
console.warn(`Failed to load task ${id} from ${indexEntry.filePath}:`, error);
|
|
927
|
+
return void 0;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Load multiple tasks by ID (on-demand loading)
|
|
932
|
+
*
|
|
933
|
+
* @param ids - Array of task IDs to load
|
|
934
|
+
* @returns Array of loaded tasks (undefined entries filtered out)
|
|
935
|
+
*/
|
|
936
|
+
async loadTasks(ids) {
|
|
937
|
+
const tasks = await Promise.all(ids.map((id) => this.loadTask(id)));
|
|
938
|
+
return tasks.filter((t) => t !== void 0);
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Get tasks by source (tasks/completed) with pagination
|
|
942
|
+
*
|
|
943
|
+
* This is a lazy-loading alternative to getTasksByStatusPaginated().
|
|
944
|
+
* Groups by directory (tasks/ or completed/) instead of status.
|
|
945
|
+
* Only loads task content for items in the requested page.
|
|
946
|
+
*
|
|
947
|
+
* @param pagination - Pagination options (applied per source)
|
|
948
|
+
* @returns Paginated tasks grouped by source
|
|
949
|
+
*/
|
|
950
|
+
async getTasksBySourcePaginated(pagination) {
|
|
951
|
+
if (!this.lazyInitialized) {
|
|
952
|
+
throw new Error("Core not lazy initialized. Call initializeLazy() first.");
|
|
953
|
+
}
|
|
954
|
+
const limit = (pagination == null ? void 0 : pagination.limit) ?? 10;
|
|
955
|
+
const offset = (pagination == null ? void 0 : pagination.offset) ?? 0;
|
|
956
|
+
const sortBy = (pagination == null ? void 0 : pagination.sortBy) ?? "title";
|
|
957
|
+
const sortDirection = (pagination == null ? void 0 : pagination.sortDirection) ?? "asc";
|
|
958
|
+
const sources = ["tasks", "completed"];
|
|
959
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
960
|
+
for (const source of sources) {
|
|
961
|
+
let entries = Array.from(this.taskIndex.values()).filter((e) => e.source === source);
|
|
962
|
+
entries = entries.sort((a, b) => {
|
|
963
|
+
const cmp = a.title.localeCompare(b.title);
|
|
964
|
+
return sortDirection === "asc" ? cmp : -cmp;
|
|
965
|
+
});
|
|
966
|
+
const total = entries.length;
|
|
967
|
+
const pageEntries = entries.slice(offset, offset + limit);
|
|
968
|
+
const items = await this.loadTasks(pageEntries.map((e) => e.id));
|
|
969
|
+
if (sortBy !== "title" && items.length > 0) {
|
|
970
|
+
const sorted = sortTasksBy(items, sortBy, sortDirection);
|
|
971
|
+
bySource.set(source, {
|
|
972
|
+
items: sorted,
|
|
973
|
+
total,
|
|
974
|
+
hasMore: offset + limit < total,
|
|
975
|
+
offset,
|
|
976
|
+
limit
|
|
977
|
+
});
|
|
978
|
+
} else {
|
|
979
|
+
bySource.set(source, {
|
|
980
|
+
items,
|
|
981
|
+
total,
|
|
982
|
+
hasMore: offset + limit < total,
|
|
983
|
+
offset,
|
|
984
|
+
limit
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return {
|
|
989
|
+
bySource,
|
|
990
|
+
sources
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
/**
|
|
994
|
+
* Load more tasks for a specific source (lazy loading)
|
|
995
|
+
*
|
|
996
|
+
* @param source - Source to load more from ("tasks" or "completed")
|
|
997
|
+
* @param currentOffset - Current offset (items already loaded)
|
|
998
|
+
* @param pagination - Pagination options
|
|
999
|
+
* @returns Paginated result for the source
|
|
1000
|
+
*/
|
|
1001
|
+
async loadMoreForSource(source, currentOffset, pagination) {
|
|
1002
|
+
if (!this.lazyInitialized) {
|
|
1003
|
+
throw new Error("Core not lazy initialized. Call initializeLazy() first.");
|
|
1004
|
+
}
|
|
1005
|
+
const limit = (pagination == null ? void 0 : pagination.limit) ?? 10;
|
|
1006
|
+
const sortDirection = (pagination == null ? void 0 : pagination.sortDirection) ?? "asc";
|
|
1007
|
+
let entries = Array.from(this.taskIndex.values()).filter((e) => e.source === source);
|
|
1008
|
+
entries = entries.sort((a, b) => {
|
|
1009
|
+
const cmp = a.title.localeCompare(b.title);
|
|
1010
|
+
return sortDirection === "asc" ? cmp : -cmp;
|
|
1011
|
+
});
|
|
1012
|
+
const total = entries.length;
|
|
1013
|
+
const pageEntries = entries.slice(currentOffset, currentOffset + limit);
|
|
1014
|
+
const items = await this.loadTasks(pageEntries.map((e) => e.id));
|
|
1015
|
+
return {
|
|
1016
|
+
items,
|
|
1017
|
+
total,
|
|
1018
|
+
hasMore: currentOffset + limit < total,
|
|
1019
|
+
offset: currentOffset,
|
|
1020
|
+
limit
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
831
1023
|
/**
|
|
832
1024
|
* Get the loaded configuration
|
|
833
1025
|
*
|
|
@@ -1155,16 +1347,20 @@ class PanelFileSystemAdapter {
|
|
|
1155
1347
|
return path.replace(/^\/+/, "").replace(/\/+$/, "").replace(/\/+/g, "/");
|
|
1156
1348
|
}
|
|
1157
1349
|
}
|
|
1158
|
-
const
|
|
1350
|
+
const SOURCE_DISPLAY_LABELS = {
|
|
1351
|
+
tasks: "Active",
|
|
1352
|
+
completed: "Completed"
|
|
1353
|
+
};
|
|
1354
|
+
const DEFAULT_SOURCES = ["tasks", "completed"];
|
|
1159
1355
|
const DEFAULT_PAGE_SIZE = 10;
|
|
1160
1356
|
function useKanbanData(options) {
|
|
1161
1357
|
const { context, actions, pageSize = DEFAULT_PAGE_SIZE } = options || {};
|
|
1162
1358
|
const [tasks, setTasks] = useState([]);
|
|
1163
|
-
const [
|
|
1359
|
+
const [sources] = useState(DEFAULT_SOURCES);
|
|
1164
1360
|
const [isLoading, setIsLoading] = useState(true);
|
|
1165
1361
|
const [error, setError] = useState(null);
|
|
1166
1362
|
const [isBacklogProject, setIsBacklogProject] = useState(false);
|
|
1167
|
-
const [
|
|
1363
|
+
const [tasksBySource, setTasksBySource] = useState(
|
|
1168
1364
|
/* @__PURE__ */ new Map()
|
|
1169
1365
|
);
|
|
1170
1366
|
const [columnStates, setColumnStates] = useState(
|
|
@@ -1213,8 +1409,7 @@ function useKanbanData(options) {
|
|
|
1213
1409
|
console.log("[useKanbanData] No context provided");
|
|
1214
1410
|
setIsBacklogProject(false);
|
|
1215
1411
|
setTasks([]);
|
|
1216
|
-
|
|
1217
|
-
setTasksByStatus(/* @__PURE__ */ new Map());
|
|
1412
|
+
setTasksBySource(/* @__PURE__ */ new Map());
|
|
1218
1413
|
setColumnStates(/* @__PURE__ */ new Map());
|
|
1219
1414
|
setIsLoading(false);
|
|
1220
1415
|
coreRef.current = null;
|
|
@@ -1228,8 +1423,7 @@ function useKanbanData(options) {
|
|
|
1228
1423
|
console.log("[useKanbanData] FileTree not available");
|
|
1229
1424
|
setIsBacklogProject(false);
|
|
1230
1425
|
setTasks([]);
|
|
1231
|
-
|
|
1232
|
-
setTasksByStatus(/* @__PURE__ */ new Map());
|
|
1426
|
+
setTasksBySource(/* @__PURE__ */ new Map());
|
|
1233
1427
|
setColumnStates(/* @__PURE__ */ new Map());
|
|
1234
1428
|
coreRef.current = null;
|
|
1235
1429
|
return;
|
|
@@ -1249,31 +1443,29 @@ function useKanbanData(options) {
|
|
|
1249
1443
|
console.log("[useKanbanData] Not a Backlog.md project");
|
|
1250
1444
|
setIsBacklogProject(false);
|
|
1251
1445
|
setTasks([]);
|
|
1252
|
-
|
|
1253
|
-
setTasksByStatus(/* @__PURE__ */ new Map());
|
|
1446
|
+
setTasksBySource(/* @__PURE__ */ new Map());
|
|
1254
1447
|
setColumnStates(/* @__PURE__ */ new Map());
|
|
1255
1448
|
coreRef.current = null;
|
|
1256
1449
|
return;
|
|
1257
1450
|
}
|
|
1258
|
-
console.log("[useKanbanData] Loading Backlog.md data with
|
|
1451
|
+
console.log("[useKanbanData] Loading Backlog.md data with lazy loading...");
|
|
1259
1452
|
setIsBacklogProject(true);
|
|
1260
|
-
await core.
|
|
1453
|
+
await core.initializeLazy(filePaths);
|
|
1261
1454
|
coreRef.current = core;
|
|
1262
|
-
const
|
|
1263
|
-
const paginatedResult = core.getTasksByStatusPaginated({
|
|
1455
|
+
const paginatedResult = await core.getTasksBySourcePaginated({
|
|
1264
1456
|
limit: pageSize,
|
|
1265
1457
|
offset: 0,
|
|
1266
1458
|
sortBy: "title",
|
|
1267
1459
|
sortDirection: "asc"
|
|
1268
1460
|
});
|
|
1269
|
-
const
|
|
1461
|
+
const newTasksBySource = /* @__PURE__ */ new Map();
|
|
1270
1462
|
const newColumnStates = /* @__PURE__ */ new Map();
|
|
1271
1463
|
let allTasks = [];
|
|
1272
|
-
for (const
|
|
1273
|
-
const columnResult = paginatedResult.
|
|
1464
|
+
for (const source of paginatedResult.sources) {
|
|
1465
|
+
const columnResult = paginatedResult.bySource.get(source);
|
|
1274
1466
|
if (columnResult) {
|
|
1275
|
-
|
|
1276
|
-
newColumnStates.set(
|
|
1467
|
+
newTasksBySource.set(source, columnResult.items);
|
|
1468
|
+
newColumnStates.set(source, {
|
|
1277
1469
|
tasks: columnResult.items,
|
|
1278
1470
|
total: columnResult.total,
|
|
1279
1471
|
hasMore: columnResult.hasMore,
|
|
@@ -1281,8 +1473,8 @@ function useKanbanData(options) {
|
|
|
1281
1473
|
});
|
|
1282
1474
|
allTasks = allTasks.concat(columnResult.items);
|
|
1283
1475
|
} else {
|
|
1284
|
-
|
|
1285
|
-
newColumnStates.set(
|
|
1476
|
+
newTasksBySource.set(source, []);
|
|
1477
|
+
newColumnStates.set(source, {
|
|
1286
1478
|
tasks: [],
|
|
1287
1479
|
total: 0,
|
|
1288
1480
|
hasMore: false,
|
|
@@ -1290,24 +1482,22 @@ function useKanbanData(options) {
|
|
|
1290
1482
|
});
|
|
1291
1483
|
}
|
|
1292
1484
|
}
|
|
1293
|
-
const totalTasks = Array.from(paginatedResult.
|
|
1485
|
+
const totalTasks = Array.from(paginatedResult.bySource.values()).reduce(
|
|
1294
1486
|
(sum, col) => sum + col.total,
|
|
1295
1487
|
0
|
|
1296
1488
|
);
|
|
1297
1489
|
console.log(
|
|
1298
|
-
`[useKanbanData] Loaded ${allTasks.length}/${totalTasks} tasks
|
|
1490
|
+
`[useKanbanData] Loaded ${allTasks.length}/${totalTasks} tasks (page size: ${pageSize})`
|
|
1299
1491
|
);
|
|
1300
|
-
setStatuses(config.statuses);
|
|
1301
1492
|
setTasks(allTasks);
|
|
1302
|
-
|
|
1493
|
+
setTasksBySource(newTasksBySource);
|
|
1303
1494
|
setColumnStates(newColumnStates);
|
|
1304
1495
|
} catch (err) {
|
|
1305
1496
|
console.error("[useKanbanData] Failed to load Backlog.md data:", err);
|
|
1306
1497
|
setError(err instanceof Error ? err.message : "Failed to load backlog data");
|
|
1307
1498
|
setIsBacklogProject(false);
|
|
1308
1499
|
setTasks([]);
|
|
1309
|
-
|
|
1310
|
-
setTasksByStatus(/* @__PURE__ */ new Map());
|
|
1500
|
+
setTasksBySource(/* @__PURE__ */ new Map());
|
|
1311
1501
|
setColumnStates(/* @__PURE__ */ new Map());
|
|
1312
1502
|
coreRef.current = null;
|
|
1313
1503
|
} finally {
|
|
@@ -1318,28 +1508,28 @@ function useKanbanData(options) {
|
|
|
1318
1508
|
loadBacklogData();
|
|
1319
1509
|
}, [loadBacklogData]);
|
|
1320
1510
|
const loadMore = useCallback(
|
|
1321
|
-
async (
|
|
1511
|
+
async (source) => {
|
|
1322
1512
|
const core = coreRef.current;
|
|
1323
1513
|
if (!core) {
|
|
1324
1514
|
console.warn("[useKanbanData] Core not available for loadMore");
|
|
1325
1515
|
return;
|
|
1326
1516
|
}
|
|
1327
|
-
const currentState = columnStates.get(
|
|
1517
|
+
const currentState = columnStates.get(source);
|
|
1328
1518
|
if (!currentState || !currentState.hasMore || currentState.isLoadingMore) {
|
|
1329
1519
|
return;
|
|
1330
1520
|
}
|
|
1331
1521
|
setColumnStates((prev) => {
|
|
1332
1522
|
const newStates = new Map(prev);
|
|
1333
|
-
const state = newStates.get(
|
|
1523
|
+
const state = newStates.get(source);
|
|
1334
1524
|
if (state) {
|
|
1335
|
-
newStates.set(
|
|
1525
|
+
newStates.set(source, { ...state, isLoadingMore: true });
|
|
1336
1526
|
}
|
|
1337
1527
|
return newStates;
|
|
1338
1528
|
});
|
|
1339
1529
|
try {
|
|
1340
1530
|
const currentOffset = currentState.tasks.length;
|
|
1341
|
-
const result = core.
|
|
1342
|
-
|
|
1531
|
+
const result = await core.loadMoreForSource(
|
|
1532
|
+
source,
|
|
1343
1533
|
currentOffset,
|
|
1344
1534
|
{
|
|
1345
1535
|
limit: pageSize,
|
|
@@ -1348,14 +1538,14 @@ function useKanbanData(options) {
|
|
|
1348
1538
|
}
|
|
1349
1539
|
);
|
|
1350
1540
|
console.log(
|
|
1351
|
-
`[useKanbanData] Loaded ${result.items.length} more tasks for "${
|
|
1541
|
+
`[useKanbanData] Loaded ${result.items.length} more tasks for "${source}" (${currentOffset + result.items.length}/${result.total})`
|
|
1352
1542
|
);
|
|
1353
1543
|
setColumnStates((prev) => {
|
|
1354
1544
|
const newStates = new Map(prev);
|
|
1355
|
-
const state = newStates.get(
|
|
1545
|
+
const state = newStates.get(source);
|
|
1356
1546
|
if (state) {
|
|
1357
1547
|
const newTasks = [...state.tasks, ...result.items];
|
|
1358
|
-
newStates.set(
|
|
1548
|
+
newStates.set(source, {
|
|
1359
1549
|
tasks: newTasks,
|
|
1360
1550
|
total: result.total,
|
|
1361
1551
|
hasMore: result.hasMore,
|
|
@@ -1364,21 +1554,21 @@ function useKanbanData(options) {
|
|
|
1364
1554
|
}
|
|
1365
1555
|
return newStates;
|
|
1366
1556
|
});
|
|
1367
|
-
|
|
1557
|
+
setTasksBySource((prev) => {
|
|
1368
1558
|
const newMap = new Map(prev);
|
|
1369
|
-
const currentTasks = newMap.get(
|
|
1370
|
-
newMap.set(
|
|
1559
|
+
const currentTasks = newMap.get(source) || [];
|
|
1560
|
+
newMap.set(source, [...currentTasks, ...result.items]);
|
|
1371
1561
|
return newMap;
|
|
1372
1562
|
});
|
|
1373
1563
|
setTasks((prev) => [...prev, ...result.items]);
|
|
1374
1564
|
} catch (err) {
|
|
1375
|
-
console.error(`[useKanbanData] Failed to load more for "${
|
|
1565
|
+
console.error(`[useKanbanData] Failed to load more for "${source}":`, err);
|
|
1376
1566
|
setError(err instanceof Error ? err.message : "Failed to load more tasks");
|
|
1377
1567
|
setColumnStates((prev) => {
|
|
1378
1568
|
const newStates = new Map(prev);
|
|
1379
|
-
const state = newStates.get(
|
|
1569
|
+
const state = newStates.get(source);
|
|
1380
1570
|
if (state) {
|
|
1381
|
-
newStates.set(
|
|
1571
|
+
newStates.set(source, { ...state, isLoadingMore: false });
|
|
1382
1572
|
}
|
|
1383
1573
|
return newStates;
|
|
1384
1574
|
});
|
|
@@ -1401,11 +1591,11 @@ function useKanbanData(options) {
|
|
|
1401
1591
|
);
|
|
1402
1592
|
return {
|
|
1403
1593
|
tasks,
|
|
1404
|
-
|
|
1594
|
+
sources,
|
|
1405
1595
|
isLoading,
|
|
1406
1596
|
error,
|
|
1407
1597
|
isBacklogProject,
|
|
1408
|
-
|
|
1598
|
+
tasksBySource,
|
|
1409
1599
|
columnStates,
|
|
1410
1600
|
loadMore,
|
|
1411
1601
|
refreshData,
|
|
@@ -1944,10 +2134,10 @@ const KanbanPanelContent = ({
|
|
|
1944
2134
|
var _a, _b;
|
|
1945
2135
|
const { theme: theme2 } = useTheme();
|
|
1946
2136
|
const [_selectedTask, setSelectedTask] = useState(null);
|
|
1947
|
-
const {
|
|
2137
|
+
const { sources, tasksBySource, columnStates, loadMore, error, isBacklogProject, refreshData } = useKanbanData({
|
|
1948
2138
|
context,
|
|
1949
2139
|
actions,
|
|
1950
|
-
pageSize:
|
|
2140
|
+
pageSize: 3
|
|
1951
2141
|
});
|
|
1952
2142
|
const handleTaskClick = (task) => {
|
|
1953
2143
|
setSelectedTask(task);
|
|
@@ -2091,21 +2281,21 @@ const KanbanPanelContent = ({
|
|
|
2091
2281
|
WebkitOverflowScrolling: "touch"
|
|
2092
2282
|
// Smooth scrolling on iOS
|
|
2093
2283
|
},
|
|
2094
|
-
children:
|
|
2095
|
-
const columnTasks =
|
|
2096
|
-
const columnState = columnStates.get(
|
|
2284
|
+
children: sources.map((source) => {
|
|
2285
|
+
const columnTasks = tasksBySource.get(source) || [];
|
|
2286
|
+
const columnState = columnStates.get(source);
|
|
2097
2287
|
return /* @__PURE__ */ jsx(
|
|
2098
2288
|
KanbanColumn,
|
|
2099
2289
|
{
|
|
2100
|
-
status,
|
|
2290
|
+
status: SOURCE_DISPLAY_LABELS[source],
|
|
2101
2291
|
tasks: columnTasks,
|
|
2102
2292
|
total: columnState == null ? void 0 : columnState.total,
|
|
2103
2293
|
hasMore: columnState == null ? void 0 : columnState.hasMore,
|
|
2104
2294
|
isLoadingMore: columnState == null ? void 0 : columnState.isLoadingMore,
|
|
2105
|
-
onLoadMore: () => loadMore(
|
|
2295
|
+
onLoadMore: () => loadMore(source),
|
|
2106
2296
|
onTaskClick: handleTaskClick
|
|
2107
2297
|
},
|
|
2108
|
-
|
|
2298
|
+
source
|
|
2109
2299
|
);
|
|
2110
2300
|
})
|
|
2111
2301
|
}
|