@industry-theme/backlogmd-kanban-panel 1.0.2 → 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.
- package/dist/panels/KanbanPanel.d.ts.map +1 -1
- package/dist/panels/kanban/components/KanbanColumn.d.ts +8 -0
- package/dist/panels/kanban/components/KanbanColumn.d.ts.map +1 -1
- package/dist/panels/kanban/hooks/useKanbanData.d.ts +24 -4
- package/dist/panels/kanban/hooks/useKanbanData.d.ts.map +1 -1
- package/dist/panels.bundle.js +664 -150
- package/dist/panels.bundle.js.map +1 -1
- package/package.json +2 -2
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,
|
|
@@ -684,6 +706,28 @@ const PRIORITY_ORDER = {
|
|
|
684
706
|
medium: 1,
|
|
685
707
|
low: 2
|
|
686
708
|
};
|
|
709
|
+
function sortTasksByTitle(tasks, direction = "asc") {
|
|
710
|
+
return [...tasks].sort((a, b) => {
|
|
711
|
+
const cmp = a.title.localeCompare(b.title);
|
|
712
|
+
return direction === "asc" ? cmp : -cmp;
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
function sortTasksBy(tasks, sortBy = "title", direction = "asc") {
|
|
716
|
+
switch (sortBy) {
|
|
717
|
+
case "title":
|
|
718
|
+
return sortTasksByTitle(tasks, direction);
|
|
719
|
+
case "createdDate":
|
|
720
|
+
return [...tasks].sort((a, b) => {
|
|
721
|
+
const cmp = a.createdDate.localeCompare(b.createdDate);
|
|
722
|
+
return direction === "asc" ? cmp : -cmp;
|
|
723
|
+
});
|
|
724
|
+
case "priority":
|
|
725
|
+
case "ordinal":
|
|
726
|
+
default:
|
|
727
|
+
const sorted = sortTasks(tasks);
|
|
728
|
+
return direction === "desc" ? sorted.reverse() : sorted;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
687
731
|
function sortTasks(tasks) {
|
|
688
732
|
return [...tasks].sort((a, b) => {
|
|
689
733
|
if (a.ordinal !== void 0 && b.ordinal !== void 0) {
|
|
@@ -725,6 +769,9 @@ class Core {
|
|
|
725
769
|
__publicField(this, "config", null);
|
|
726
770
|
__publicField(this, "tasks", /* @__PURE__ */ new Map());
|
|
727
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);
|
|
728
775
|
this.projectRoot = options.projectRoot;
|
|
729
776
|
this.fs = options.adapters.fs;
|
|
730
777
|
}
|
|
@@ -806,6 +853,173 @@ class Core {
|
|
|
806
853
|
}
|
|
807
854
|
this.initialized = true;
|
|
808
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
|
+
}
|
|
809
1023
|
/**
|
|
810
1024
|
* Get the loaded configuration
|
|
811
1025
|
*
|
|
@@ -863,6 +1077,99 @@ class Core {
|
|
|
863
1077
|
this.ensureInitialized();
|
|
864
1078
|
return this.tasks.get(id);
|
|
865
1079
|
}
|
|
1080
|
+
/**
|
|
1081
|
+
* List tasks with pagination
|
|
1082
|
+
*
|
|
1083
|
+
* @param filter - Filter and pagination options
|
|
1084
|
+
* @returns Paginated result with tasks
|
|
1085
|
+
*/
|
|
1086
|
+
listTasksPaginated(filter) {
|
|
1087
|
+
this.ensureInitialized();
|
|
1088
|
+
let tasks = this.applyFilters(Array.from(this.tasks.values()), filter);
|
|
1089
|
+
const pagination = (filter == null ? void 0 : filter.pagination) ?? {};
|
|
1090
|
+
const sortBy = pagination.sortBy ?? "title";
|
|
1091
|
+
const sortDirection = pagination.sortDirection ?? "asc";
|
|
1092
|
+
tasks = sortTasksBy(tasks, sortBy, sortDirection);
|
|
1093
|
+
const limit = pagination.limit ?? 10;
|
|
1094
|
+
const offset = pagination.offset ?? 0;
|
|
1095
|
+
const total = tasks.length;
|
|
1096
|
+
const items = tasks.slice(offset, offset + limit);
|
|
1097
|
+
return {
|
|
1098
|
+
items,
|
|
1099
|
+
total,
|
|
1100
|
+
hasMore: offset + limit < total,
|
|
1101
|
+
offset,
|
|
1102
|
+
limit
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Get tasks by status with pagination per column
|
|
1107
|
+
*
|
|
1108
|
+
* @param pagination - Pagination options (applied per status)
|
|
1109
|
+
* @returns Paginated tasks grouped by status
|
|
1110
|
+
*/
|
|
1111
|
+
getTasksByStatusPaginated(pagination) {
|
|
1112
|
+
this.ensureInitialized();
|
|
1113
|
+
const limit = (pagination == null ? void 0 : pagination.limit) ?? 10;
|
|
1114
|
+
const offset = (pagination == null ? void 0 : pagination.offset) ?? 0;
|
|
1115
|
+
const sortBy = (pagination == null ? void 0 : pagination.sortBy) ?? "title";
|
|
1116
|
+
const sortDirection = (pagination == null ? void 0 : pagination.sortDirection) ?? "asc";
|
|
1117
|
+
const byStatus = /* @__PURE__ */ new Map();
|
|
1118
|
+
const allGrouped = /* @__PURE__ */ new Map();
|
|
1119
|
+
for (const status of this.config.statuses) {
|
|
1120
|
+
allGrouped.set(status, []);
|
|
1121
|
+
}
|
|
1122
|
+
for (const task of this.tasks.values()) {
|
|
1123
|
+
const list = allGrouped.get(task.status);
|
|
1124
|
+
if (list) {
|
|
1125
|
+
list.push(task);
|
|
1126
|
+
} else {
|
|
1127
|
+
allGrouped.set(task.status, [task]);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
for (const status of this.config.statuses) {
|
|
1131
|
+
let tasks = allGrouped.get(status) ?? [];
|
|
1132
|
+
tasks = sortTasksBy(tasks, sortBy, sortDirection);
|
|
1133
|
+
const total = tasks.length;
|
|
1134
|
+
const items = tasks.slice(offset, offset + limit);
|
|
1135
|
+
byStatus.set(status, {
|
|
1136
|
+
items,
|
|
1137
|
+
total,
|
|
1138
|
+
hasMore: offset + limit < total,
|
|
1139
|
+
offset,
|
|
1140
|
+
limit
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
return {
|
|
1144
|
+
byStatus,
|
|
1145
|
+
statuses: this.config.statuses
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Load more tasks for a specific status
|
|
1150
|
+
*
|
|
1151
|
+
* @param status - Status column to load more from
|
|
1152
|
+
* @param currentOffset - Current offset (items already loaded)
|
|
1153
|
+
* @param pagination - Pagination options (limit, sortBy, sortDirection)
|
|
1154
|
+
* @returns Paginated result for the status
|
|
1155
|
+
*/
|
|
1156
|
+
loadMoreForStatus(status, currentOffset, pagination) {
|
|
1157
|
+
this.ensureInitialized();
|
|
1158
|
+
const limit = (pagination == null ? void 0 : pagination.limit) ?? 10;
|
|
1159
|
+
const sortBy = (pagination == null ? void 0 : pagination.sortBy) ?? "title";
|
|
1160
|
+
const sortDirection = (pagination == null ? void 0 : pagination.sortDirection) ?? "asc";
|
|
1161
|
+
let tasks = Array.from(this.tasks.values()).filter((t) => t.status === status);
|
|
1162
|
+
tasks = sortTasksBy(tasks, sortBy, sortDirection);
|
|
1163
|
+
const total = tasks.length;
|
|
1164
|
+
const items = tasks.slice(currentOffset, currentOffset + limit);
|
|
1165
|
+
return {
|
|
1166
|
+
items,
|
|
1167
|
+
total,
|
|
1168
|
+
hasMore: currentOffset + limit < total,
|
|
1169
|
+
offset: currentOffset,
|
|
1170
|
+
limit
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
866
1173
|
/**
|
|
867
1174
|
* Reload all tasks from disk
|
|
868
1175
|
*
|
|
@@ -879,6 +1186,27 @@ class Core {
|
|
|
879
1186
|
throw new Error("Core not initialized. Call initialize() first.");
|
|
880
1187
|
}
|
|
881
1188
|
}
|
|
1189
|
+
applyFilters(tasks, filter) {
|
|
1190
|
+
if (!filter)
|
|
1191
|
+
return tasks;
|
|
1192
|
+
let result = tasks;
|
|
1193
|
+
if (filter.status) {
|
|
1194
|
+
result = result.filter((t) => t.status === filter.status);
|
|
1195
|
+
}
|
|
1196
|
+
if (filter.assignee) {
|
|
1197
|
+
result = result.filter((t) => t.assignee.includes(filter.assignee));
|
|
1198
|
+
}
|
|
1199
|
+
if (filter.priority) {
|
|
1200
|
+
result = result.filter((t) => t.priority === filter.priority);
|
|
1201
|
+
}
|
|
1202
|
+
if (filter.labels && filter.labels.length > 0) {
|
|
1203
|
+
result = result.filter((t) => filter.labels.some((label) => t.labels.includes(label)));
|
|
1204
|
+
}
|
|
1205
|
+
if (filter.parentTaskId) {
|
|
1206
|
+
result = result.filter((t) => t.parentTaskId === filter.parentTaskId);
|
|
1207
|
+
}
|
|
1208
|
+
return result;
|
|
1209
|
+
}
|
|
882
1210
|
async loadTasksFromDirectory(dir, source) {
|
|
883
1211
|
const entries = await this.fs.readDir(dir);
|
|
884
1212
|
for (const entry of entries) {
|
|
@@ -1019,17 +1347,26 @@ class PanelFileSystemAdapter {
|
|
|
1019
1347
|
return path.replace(/^\/+/, "").replace(/\/+$/, "").replace(/\/+/g, "/");
|
|
1020
1348
|
}
|
|
1021
1349
|
}
|
|
1022
|
-
const
|
|
1350
|
+
const SOURCE_DISPLAY_LABELS = {
|
|
1351
|
+
tasks: "Active",
|
|
1352
|
+
completed: "Completed"
|
|
1353
|
+
};
|
|
1354
|
+
const DEFAULT_SOURCES = ["tasks", "completed"];
|
|
1355
|
+
const DEFAULT_PAGE_SIZE = 10;
|
|
1023
1356
|
function useKanbanData(options) {
|
|
1024
|
-
const { context, actions } = options || {};
|
|
1357
|
+
const { context, actions, pageSize = DEFAULT_PAGE_SIZE } = options || {};
|
|
1025
1358
|
const [tasks, setTasks] = useState([]);
|
|
1026
|
-
const [
|
|
1359
|
+
const [sources] = useState(DEFAULT_SOURCES);
|
|
1027
1360
|
const [isLoading, setIsLoading] = useState(true);
|
|
1028
1361
|
const [error, setError] = useState(null);
|
|
1029
1362
|
const [isBacklogProject, setIsBacklogProject] = useState(false);
|
|
1030
|
-
const [
|
|
1363
|
+
const [tasksBySource, setTasksBySource] = useState(
|
|
1364
|
+
/* @__PURE__ */ new Map()
|
|
1365
|
+
);
|
|
1366
|
+
const [columnStates, setColumnStates] = useState(
|
|
1031
1367
|
/* @__PURE__ */ new Map()
|
|
1032
1368
|
);
|
|
1369
|
+
const coreRef = useRef(null);
|
|
1033
1370
|
const activeFilePathRef = useRef(null);
|
|
1034
1371
|
const contextRef = useRef(context);
|
|
1035
1372
|
const actionsRef = useRef(actions);
|
|
@@ -1072,9 +1409,10 @@ function useKanbanData(options) {
|
|
|
1072
1409
|
console.log("[useKanbanData] No context provided");
|
|
1073
1410
|
setIsBacklogProject(false);
|
|
1074
1411
|
setTasks([]);
|
|
1075
|
-
|
|
1076
|
-
|
|
1412
|
+
setTasksBySource(/* @__PURE__ */ new Map());
|
|
1413
|
+
setColumnStates(/* @__PURE__ */ new Map());
|
|
1077
1414
|
setIsLoading(false);
|
|
1415
|
+
coreRef.current = null;
|
|
1078
1416
|
return;
|
|
1079
1417
|
}
|
|
1080
1418
|
setIsLoading(true);
|
|
@@ -1085,8 +1423,9 @@ function useKanbanData(options) {
|
|
|
1085
1423
|
console.log("[useKanbanData] FileTree not available");
|
|
1086
1424
|
setIsBacklogProject(false);
|
|
1087
1425
|
setTasks([]);
|
|
1088
|
-
|
|
1089
|
-
|
|
1426
|
+
setTasksBySource(/* @__PURE__ */ new Map());
|
|
1427
|
+
setColumnStates(/* @__PURE__ */ new Map());
|
|
1428
|
+
coreRef.current = null;
|
|
1090
1429
|
return;
|
|
1091
1430
|
}
|
|
1092
1431
|
const files = fileTreeSlice.data.allFiles;
|
|
@@ -1104,36 +1443,139 @@ function useKanbanData(options) {
|
|
|
1104
1443
|
console.log("[useKanbanData] Not a Backlog.md project");
|
|
1105
1444
|
setIsBacklogProject(false);
|
|
1106
1445
|
setTasks([]);
|
|
1107
|
-
|
|
1108
|
-
|
|
1446
|
+
setTasksBySource(/* @__PURE__ */ new Map());
|
|
1447
|
+
setColumnStates(/* @__PURE__ */ new Map());
|
|
1448
|
+
coreRef.current = null;
|
|
1109
1449
|
return;
|
|
1110
1450
|
}
|
|
1111
|
-
console.log("[useKanbanData] Loading Backlog.md data...");
|
|
1451
|
+
console.log("[useKanbanData] Loading Backlog.md data with lazy loading...");
|
|
1112
1452
|
setIsBacklogProject(true);
|
|
1113
|
-
await core.
|
|
1114
|
-
|
|
1115
|
-
const
|
|
1116
|
-
|
|
1453
|
+
await core.initializeLazy(filePaths);
|
|
1454
|
+
coreRef.current = core;
|
|
1455
|
+
const paginatedResult = await core.getTasksBySourcePaginated({
|
|
1456
|
+
limit: pageSize,
|
|
1457
|
+
offset: 0,
|
|
1458
|
+
sortBy: "title",
|
|
1459
|
+
sortDirection: "asc"
|
|
1460
|
+
});
|
|
1461
|
+
const newTasksBySource = /* @__PURE__ */ new Map();
|
|
1462
|
+
const newColumnStates = /* @__PURE__ */ new Map();
|
|
1463
|
+
let allTasks = [];
|
|
1464
|
+
for (const source of paginatedResult.sources) {
|
|
1465
|
+
const columnResult = paginatedResult.bySource.get(source);
|
|
1466
|
+
if (columnResult) {
|
|
1467
|
+
newTasksBySource.set(source, columnResult.items);
|
|
1468
|
+
newColumnStates.set(source, {
|
|
1469
|
+
tasks: columnResult.items,
|
|
1470
|
+
total: columnResult.total,
|
|
1471
|
+
hasMore: columnResult.hasMore,
|
|
1472
|
+
isLoadingMore: false
|
|
1473
|
+
});
|
|
1474
|
+
allTasks = allTasks.concat(columnResult.items);
|
|
1475
|
+
} else {
|
|
1476
|
+
newTasksBySource.set(source, []);
|
|
1477
|
+
newColumnStates.set(source, {
|
|
1478
|
+
tasks: [],
|
|
1479
|
+
total: 0,
|
|
1480
|
+
hasMore: false,
|
|
1481
|
+
isLoadingMore: false
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
const totalTasks = Array.from(paginatedResult.bySource.values()).reduce(
|
|
1486
|
+
(sum, col) => sum + col.total,
|
|
1487
|
+
0
|
|
1488
|
+
);
|
|
1117
1489
|
console.log(
|
|
1118
|
-
`[useKanbanData] Loaded ${allTasks.length} tasks
|
|
1490
|
+
`[useKanbanData] Loaded ${allTasks.length}/${totalTasks} tasks (page size: ${pageSize})`
|
|
1119
1491
|
);
|
|
1120
|
-
setStatuses(config.statuses);
|
|
1121
1492
|
setTasks(allTasks);
|
|
1122
|
-
|
|
1493
|
+
setTasksBySource(newTasksBySource);
|
|
1494
|
+
setColumnStates(newColumnStates);
|
|
1123
1495
|
} catch (err) {
|
|
1124
1496
|
console.error("[useKanbanData] Failed to load Backlog.md data:", err);
|
|
1125
1497
|
setError(err instanceof Error ? err.message : "Failed to load backlog data");
|
|
1126
1498
|
setIsBacklogProject(false);
|
|
1127
1499
|
setTasks([]);
|
|
1128
|
-
|
|
1129
|
-
|
|
1500
|
+
setTasksBySource(/* @__PURE__ */ new Map());
|
|
1501
|
+
setColumnStates(/* @__PURE__ */ new Map());
|
|
1502
|
+
coreRef.current = null;
|
|
1130
1503
|
} finally {
|
|
1131
1504
|
setIsLoading(false);
|
|
1132
1505
|
}
|
|
1133
|
-
}, [context, actions, fetchFileContent]);
|
|
1506
|
+
}, [context, actions, fetchFileContent, pageSize]);
|
|
1134
1507
|
useEffect(() => {
|
|
1135
1508
|
loadBacklogData();
|
|
1136
1509
|
}, [loadBacklogData]);
|
|
1510
|
+
const loadMore = useCallback(
|
|
1511
|
+
async (source) => {
|
|
1512
|
+
const core = coreRef.current;
|
|
1513
|
+
if (!core) {
|
|
1514
|
+
console.warn("[useKanbanData] Core not available for loadMore");
|
|
1515
|
+
return;
|
|
1516
|
+
}
|
|
1517
|
+
const currentState = columnStates.get(source);
|
|
1518
|
+
if (!currentState || !currentState.hasMore || currentState.isLoadingMore) {
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1521
|
+
setColumnStates((prev) => {
|
|
1522
|
+
const newStates = new Map(prev);
|
|
1523
|
+
const state = newStates.get(source);
|
|
1524
|
+
if (state) {
|
|
1525
|
+
newStates.set(source, { ...state, isLoadingMore: true });
|
|
1526
|
+
}
|
|
1527
|
+
return newStates;
|
|
1528
|
+
});
|
|
1529
|
+
try {
|
|
1530
|
+
const currentOffset = currentState.tasks.length;
|
|
1531
|
+
const result = await core.loadMoreForSource(
|
|
1532
|
+
source,
|
|
1533
|
+
currentOffset,
|
|
1534
|
+
{
|
|
1535
|
+
limit: pageSize,
|
|
1536
|
+
sortBy: "title",
|
|
1537
|
+
sortDirection: "asc"
|
|
1538
|
+
}
|
|
1539
|
+
);
|
|
1540
|
+
console.log(
|
|
1541
|
+
`[useKanbanData] Loaded ${result.items.length} more tasks for "${source}" (${currentOffset + result.items.length}/${result.total})`
|
|
1542
|
+
);
|
|
1543
|
+
setColumnStates((prev) => {
|
|
1544
|
+
const newStates = new Map(prev);
|
|
1545
|
+
const state = newStates.get(source);
|
|
1546
|
+
if (state) {
|
|
1547
|
+
const newTasks = [...state.tasks, ...result.items];
|
|
1548
|
+
newStates.set(source, {
|
|
1549
|
+
tasks: newTasks,
|
|
1550
|
+
total: result.total,
|
|
1551
|
+
hasMore: result.hasMore,
|
|
1552
|
+
isLoadingMore: false
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
return newStates;
|
|
1556
|
+
});
|
|
1557
|
+
setTasksBySource((prev) => {
|
|
1558
|
+
const newMap = new Map(prev);
|
|
1559
|
+
const currentTasks = newMap.get(source) || [];
|
|
1560
|
+
newMap.set(source, [...currentTasks, ...result.items]);
|
|
1561
|
+
return newMap;
|
|
1562
|
+
});
|
|
1563
|
+
setTasks((prev) => [...prev, ...result.items]);
|
|
1564
|
+
} catch (err) {
|
|
1565
|
+
console.error(`[useKanbanData] Failed to load more for "${source}":`, err);
|
|
1566
|
+
setError(err instanceof Error ? err.message : "Failed to load more tasks");
|
|
1567
|
+
setColumnStates((prev) => {
|
|
1568
|
+
const newStates = new Map(prev);
|
|
1569
|
+
const state = newStates.get(source);
|
|
1570
|
+
if (state) {
|
|
1571
|
+
newStates.set(source, { ...state, isLoadingMore: false });
|
|
1572
|
+
}
|
|
1573
|
+
return newStates;
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1576
|
+
},
|
|
1577
|
+
[columnStates, pageSize]
|
|
1578
|
+
);
|
|
1137
1579
|
const refreshData = useCallback(async () => {
|
|
1138
1580
|
await loadBacklogData();
|
|
1139
1581
|
}, [loadBacklogData]);
|
|
@@ -1149,11 +1591,13 @@ function useKanbanData(options) {
|
|
|
1149
1591
|
);
|
|
1150
1592
|
return {
|
|
1151
1593
|
tasks,
|
|
1152
|
-
|
|
1594
|
+
sources,
|
|
1153
1595
|
isLoading,
|
|
1154
1596
|
error,
|
|
1155
1597
|
isBacklogProject,
|
|
1156
|
-
|
|
1598
|
+
tasksBySource,
|
|
1599
|
+
columnStates,
|
|
1600
|
+
loadMore,
|
|
1157
1601
|
refreshData,
|
|
1158
1602
|
updateTaskStatus
|
|
1159
1603
|
};
|
|
@@ -1161,6 +1605,10 @@ function useKanbanData(options) {
|
|
|
1161
1605
|
const KanbanColumn = ({
|
|
1162
1606
|
status,
|
|
1163
1607
|
tasks,
|
|
1608
|
+
total,
|
|
1609
|
+
hasMore = false,
|
|
1610
|
+
isLoadingMore = false,
|
|
1611
|
+
onLoadMore,
|
|
1164
1612
|
onTaskClick
|
|
1165
1613
|
}) => {
|
|
1166
1614
|
const { theme: theme2 } = useTheme();
|
|
@@ -1176,6 +1624,7 @@ const KanbanColumn = ({
|
|
|
1176
1624
|
return theme2.colors.border;
|
|
1177
1625
|
}
|
|
1178
1626
|
};
|
|
1627
|
+
const remaining = total !== void 0 ? total - tasks.length : 0;
|
|
1179
1628
|
return /* @__PURE__ */ jsxs(
|
|
1180
1629
|
"div",
|
|
1181
1630
|
{
|
|
@@ -1224,13 +1673,13 @@ const KanbanColumn = ({
|
|
|
1224
1673
|
padding: "2px 8px",
|
|
1225
1674
|
borderRadius: theme2.radii[1]
|
|
1226
1675
|
},
|
|
1227
|
-
children: tasks.length
|
|
1676
|
+
children: total !== void 0 ? `${tasks.length}/${total}` : tasks.length
|
|
1228
1677
|
}
|
|
1229
1678
|
)
|
|
1230
1679
|
]
|
|
1231
1680
|
}
|
|
1232
1681
|
),
|
|
1233
|
-
/* @__PURE__ */
|
|
1682
|
+
/* @__PURE__ */ jsxs(
|
|
1234
1683
|
"div",
|
|
1235
1684
|
{
|
|
1236
1685
|
style: {
|
|
@@ -1241,131 +1690,190 @@ const KanbanColumn = ({
|
|
|
1241
1690
|
overflowY: "auto",
|
|
1242
1691
|
WebkitOverflowScrolling: "touch"
|
|
1243
1692
|
},
|
|
1244
|
-
children:
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
},
|
|
1265
|
-
onMouseLeave: (e) => {
|
|
1266
|
-
if (onTaskClick) {
|
|
1267
|
-
e.currentTarget.style.transform = "translateY(0)";
|
|
1268
|
-
e.currentTarget.style.boxShadow = "none";
|
|
1269
|
-
}
|
|
1270
|
-
},
|
|
1271
|
-
children: [
|
|
1272
|
-
/* @__PURE__ */ jsx(
|
|
1273
|
-
"h4",
|
|
1274
|
-
{
|
|
1275
|
-
style: {
|
|
1276
|
-
margin: "0 0 8px 0",
|
|
1277
|
-
fontSize: theme2.fontSizes[2],
|
|
1278
|
-
color: theme2.colors.text,
|
|
1279
|
-
fontWeight: theme2.fontWeights.medium
|
|
1280
|
-
},
|
|
1281
|
-
children: task.title
|
|
1693
|
+
children: [
|
|
1694
|
+
tasks.map((task) => /* @__PURE__ */ jsxs(
|
|
1695
|
+
"div",
|
|
1696
|
+
{
|
|
1697
|
+
onClick: () => onTaskClick == null ? void 0 : onTaskClick(task),
|
|
1698
|
+
style: {
|
|
1699
|
+
background: theme2.colors.surface,
|
|
1700
|
+
borderRadius: theme2.radii[2],
|
|
1701
|
+
padding: "12px",
|
|
1702
|
+
border: `1px solid ${theme2.colors.border}`,
|
|
1703
|
+
borderLeft: `4px solid ${getPriorityColor(task.priority)}`,
|
|
1704
|
+
cursor: onTaskClick ? "pointer" : "default",
|
|
1705
|
+
transition: "all 0.2s ease",
|
|
1706
|
+
minHeight: "44px"
|
|
1707
|
+
// Minimum touch target size for mobile
|
|
1708
|
+
},
|
|
1709
|
+
onMouseEnter: (e) => {
|
|
1710
|
+
if (onTaskClick) {
|
|
1711
|
+
e.currentTarget.style.transform = "translateY(-2px)";
|
|
1712
|
+
e.currentTarget.style.boxShadow = `0 4px 8px ${theme2.colors.border}`;
|
|
1282
1713
|
}
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
style
|
|
1288
|
-
margin: "0 0 8px 0",
|
|
1289
|
-
fontSize: theme2.fontSizes[1],
|
|
1290
|
-
color: theme2.colors.textSecondary,
|
|
1291
|
-
overflow: "hidden",
|
|
1292
|
-
textOverflow: "ellipsis",
|
|
1293
|
-
display: "-webkit-box",
|
|
1294
|
-
WebkitLineClamp: 2,
|
|
1295
|
-
WebkitBoxOrient: "vertical",
|
|
1296
|
-
lineHeight: "1.4"
|
|
1297
|
-
},
|
|
1298
|
-
children: task.description
|
|
1714
|
+
},
|
|
1715
|
+
onMouseLeave: (e) => {
|
|
1716
|
+
if (onTaskClick) {
|
|
1717
|
+
e.currentTarget.style.transform = "translateY(0)";
|
|
1718
|
+
e.currentTarget.style.boxShadow = "none";
|
|
1299
1719
|
}
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
children: task.labels.map((label) => /* @__PURE__ */ jsx(
|
|
1311
|
-
"span",
|
|
1312
|
-
{
|
|
1313
|
-
style: {
|
|
1314
|
-
fontSize: theme2.fontSizes[0],
|
|
1315
|
-
color: theme2.colors.primary,
|
|
1316
|
-
background: `${theme2.colors.primary}20`,
|
|
1317
|
-
padding: "2px 8px",
|
|
1318
|
-
borderRadius: theme2.radii[1],
|
|
1319
|
-
fontWeight: theme2.fontWeights.medium
|
|
1320
|
-
},
|
|
1321
|
-
children: label
|
|
1720
|
+
},
|
|
1721
|
+
children: [
|
|
1722
|
+
/* @__PURE__ */ jsx(
|
|
1723
|
+
"h4",
|
|
1724
|
+
{
|
|
1725
|
+
style: {
|
|
1726
|
+
margin: "0 0 8px 0",
|
|
1727
|
+
fontSize: theme2.fontSizes[2],
|
|
1728
|
+
color: theme2.colors.text,
|
|
1729
|
+
fontWeight: theme2.fontWeights.medium
|
|
1322
1730
|
},
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1731
|
+
children: task.title
|
|
1732
|
+
}
|
|
1733
|
+
),
|
|
1734
|
+
task.description && /* @__PURE__ */ jsx(
|
|
1735
|
+
"p",
|
|
1736
|
+
{
|
|
1737
|
+
style: {
|
|
1738
|
+
margin: "0 0 8px 0",
|
|
1739
|
+
fontSize: theme2.fontSizes[1],
|
|
1740
|
+
color: theme2.colors.textSecondary,
|
|
1741
|
+
overflow: "hidden",
|
|
1742
|
+
textOverflow: "ellipsis",
|
|
1743
|
+
display: "-webkit-box",
|
|
1744
|
+
WebkitLineClamp: 2,
|
|
1745
|
+
WebkitBoxOrient: "vertical",
|
|
1746
|
+
lineHeight: "1.4"
|
|
1747
|
+
},
|
|
1748
|
+
children: task.description
|
|
1749
|
+
}
|
|
1750
|
+
),
|
|
1751
|
+
task.labels && task.labels.length > 0 && /* @__PURE__ */ jsx(
|
|
1752
|
+
"div",
|
|
1753
|
+
{
|
|
1754
|
+
style: {
|
|
1755
|
+
display: "flex",
|
|
1756
|
+
gap: "4px",
|
|
1757
|
+
flexWrap: "wrap",
|
|
1758
|
+
marginBottom: "8px"
|
|
1759
|
+
},
|
|
1760
|
+
children: task.labels.map((label) => /* @__PURE__ */ jsx(
|
|
1348
1761
|
"span",
|
|
1349
1762
|
{
|
|
1350
1763
|
style: {
|
|
1351
|
-
|
|
1764
|
+
fontSize: theme2.fontSizes[0],
|
|
1765
|
+
color: theme2.colors.primary,
|
|
1766
|
+
background: `${theme2.colors.primary}20`,
|
|
1767
|
+
padding: "2px 8px",
|
|
1768
|
+
borderRadius: theme2.radii[1],
|
|
1769
|
+
fontWeight: theme2.fontWeights.medium
|
|
1352
1770
|
},
|
|
1353
|
-
children:
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1771
|
+
children: label
|
|
1772
|
+
},
|
|
1773
|
+
label
|
|
1774
|
+
))
|
|
1775
|
+
}
|
|
1776
|
+
),
|
|
1777
|
+
/* @__PURE__ */ jsxs(
|
|
1778
|
+
"div",
|
|
1779
|
+
{
|
|
1780
|
+
style: {
|
|
1781
|
+
display: "flex",
|
|
1782
|
+
alignItems: "center",
|
|
1783
|
+
justifyContent: "space-between",
|
|
1784
|
+
fontSize: theme2.fontSizes[0],
|
|
1785
|
+
color: theme2.colors.textMuted
|
|
1786
|
+
},
|
|
1787
|
+
children: [
|
|
1788
|
+
/* @__PURE__ */ jsx(
|
|
1789
|
+
"span",
|
|
1790
|
+
{
|
|
1791
|
+
style: {
|
|
1792
|
+
fontFamily: theme2.fonts.monospace
|
|
1793
|
+
},
|
|
1794
|
+
children: task.id
|
|
1795
|
+
}
|
|
1796
|
+
),
|
|
1797
|
+
task.assignee && task.assignee.length > 0 && /* @__PURE__ */ jsxs(
|
|
1798
|
+
"span",
|
|
1799
|
+
{
|
|
1800
|
+
style: {
|
|
1801
|
+
color: theme2.colors.textSecondary
|
|
1802
|
+
},
|
|
1803
|
+
children: [
|
|
1804
|
+
task.assignee.length,
|
|
1805
|
+
" assignee",
|
|
1806
|
+
task.assignee.length !== 1 ? "s" : ""
|
|
1807
|
+
]
|
|
1808
|
+
}
|
|
1809
|
+
)
|
|
1810
|
+
]
|
|
1811
|
+
}
|
|
1812
|
+
)
|
|
1813
|
+
]
|
|
1814
|
+
},
|
|
1815
|
+
task.id
|
|
1816
|
+
)),
|
|
1817
|
+
hasMore && onLoadMore && /* @__PURE__ */ jsx(
|
|
1818
|
+
"button",
|
|
1819
|
+
{
|
|
1820
|
+
onClick: onLoadMore,
|
|
1821
|
+
disabled: isLoadingMore,
|
|
1822
|
+
style: {
|
|
1823
|
+
background: theme2.colors.background,
|
|
1824
|
+
border: `1px dashed ${theme2.colors.border}`,
|
|
1825
|
+
borderRadius: theme2.radii[2],
|
|
1826
|
+
padding: "12px",
|
|
1827
|
+
cursor: isLoadingMore ? "wait" : "pointer",
|
|
1828
|
+
color: theme2.colors.textSecondary,
|
|
1829
|
+
fontSize: theme2.fontSizes[1],
|
|
1830
|
+
fontWeight: theme2.fontWeights.medium,
|
|
1831
|
+
transition: "all 0.2s ease",
|
|
1832
|
+
minHeight: "44px",
|
|
1833
|
+
display: "flex",
|
|
1834
|
+
alignItems: "center",
|
|
1835
|
+
justifyContent: "center",
|
|
1836
|
+
gap: "8px"
|
|
1837
|
+
},
|
|
1838
|
+
onMouseEnter: (e) => {
|
|
1839
|
+
if (!isLoadingMore) {
|
|
1840
|
+
e.currentTarget.style.background = theme2.colors.backgroundSecondary;
|
|
1841
|
+
e.currentTarget.style.borderColor = theme2.colors.primary;
|
|
1842
|
+
e.currentTarget.style.color = theme2.colors.primary;
|
|
1361
1843
|
}
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1844
|
+
},
|
|
1845
|
+
onMouseLeave: (e) => {
|
|
1846
|
+
e.currentTarget.style.background = theme2.colors.background;
|
|
1847
|
+
e.currentTarget.style.borderColor = theme2.colors.border;
|
|
1848
|
+
e.currentTarget.style.color = theme2.colors.textSecondary;
|
|
1849
|
+
},
|
|
1850
|
+
children: isLoadingMore ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1851
|
+
/* @__PURE__ */ jsx(
|
|
1852
|
+
"span",
|
|
1853
|
+
{
|
|
1854
|
+
style: {
|
|
1855
|
+
display: "inline-block",
|
|
1856
|
+
width: "14px",
|
|
1857
|
+
height: "14px",
|
|
1858
|
+
border: `2px solid ${theme2.colors.border}`,
|
|
1859
|
+
borderTopColor: theme2.colors.primary,
|
|
1860
|
+
borderRadius: "50%",
|
|
1861
|
+
animation: "spin 1s linear infinite"
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
),
|
|
1865
|
+
"Loading..."
|
|
1866
|
+
] }) : `Load more (${remaining} remaining)`
|
|
1867
|
+
}
|
|
1868
|
+
)
|
|
1869
|
+
]
|
|
1367
1870
|
}
|
|
1368
|
-
)
|
|
1871
|
+
),
|
|
1872
|
+
/* @__PURE__ */ jsx("style", { children: `
|
|
1873
|
+
@keyframes spin {
|
|
1874
|
+
to { transform: rotate(360deg); }
|
|
1875
|
+
}
|
|
1876
|
+
` })
|
|
1369
1877
|
]
|
|
1370
1878
|
}
|
|
1371
1879
|
);
|
|
@@ -1626,9 +2134,10 @@ const KanbanPanelContent = ({
|
|
|
1626
2134
|
var _a, _b;
|
|
1627
2135
|
const { theme: theme2 } = useTheme();
|
|
1628
2136
|
const [_selectedTask, setSelectedTask] = useState(null);
|
|
1629
|
-
const {
|
|
2137
|
+
const { sources, tasksBySource, columnStates, loadMore, error, isBacklogProject, refreshData } = useKanbanData({
|
|
1630
2138
|
context,
|
|
1631
|
-
actions
|
|
2139
|
+
actions,
|
|
2140
|
+
pageSize: 3
|
|
1632
2141
|
});
|
|
1633
2142
|
const handleTaskClick = (task) => {
|
|
1634
2143
|
setSelectedTask(task);
|
|
@@ -1772,16 +2281,21 @@ const KanbanPanelContent = ({
|
|
|
1772
2281
|
WebkitOverflowScrolling: "touch"
|
|
1773
2282
|
// Smooth scrolling on iOS
|
|
1774
2283
|
},
|
|
1775
|
-
children:
|
|
1776
|
-
const columnTasks =
|
|
2284
|
+
children: sources.map((source) => {
|
|
2285
|
+
const columnTasks = tasksBySource.get(source) || [];
|
|
2286
|
+
const columnState = columnStates.get(source);
|
|
1777
2287
|
return /* @__PURE__ */ jsx(
|
|
1778
2288
|
KanbanColumn,
|
|
1779
2289
|
{
|
|
1780
|
-
status,
|
|
2290
|
+
status: SOURCE_DISPLAY_LABELS[source],
|
|
1781
2291
|
tasks: columnTasks,
|
|
2292
|
+
total: columnState == null ? void 0 : columnState.total,
|
|
2293
|
+
hasMore: columnState == null ? void 0 : columnState.hasMore,
|
|
2294
|
+
isLoadingMore: columnState == null ? void 0 : columnState.isLoadingMore,
|
|
2295
|
+
onLoadMore: () => loadMore(source),
|
|
1782
2296
|
onTaskClick: handleTaskClick
|
|
1783
2297
|
},
|
|
1784
|
-
|
|
2298
|
+
source
|
|
1785
2299
|
);
|
|
1786
2300
|
})
|
|
1787
2301
|
}
|