Compare commits

..

2 Commits

View File

@@ -4,20 +4,23 @@
<div v-if="isLoadingInitialData">Загружаем карточки...</div> <div v-if="isLoadingInitialData">Загружаем карточки...</div>
<div v-else> <div v-else>
<div v-if="userstoriesForProject && userstoriesForProject.length === 0">Нет карточек на доске.</div> <div v-if="userstoriesForProject && userstoriesForProject.length === 0">Нет карточек на доске.</div>
<div v-else-if="userstoriesForProject" class="userstory-table-container"> <div v-else-if="sortedUserstories" class="userstory-table-container">
<table> <table>
<thead> <thead>
<tr> <tr>
<th v-for="header in tableHeaders" :key="header.key">{{ header.label }}</th> <th v-for="header in tableHeaders" :key="header.key" @click="handleSort(header.key)" style="cursor: pointer">
{{ header.label }}
<span v-if="sortKey === header.key">{{ sortOrder === "asc" ? "▲" : "▼" }}</span>
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="userstory in userstoriesForProject" :key="userstory.id"> <tr v-for="userstory in sortedUserstories" :key="userstory.id">
<td v-for="header in tableHeaders" :key="`${userstory.id}-${header.key}`"> <td v-for="header in tableHeaders" :key="`${userstory.id}-${header.key}`">
{{ getCellValue(userstory, header) }} {{ getCellValue(userstory, header) }}
</td> </td>
</tr> </tr>
<tr v-if="isLoadingAttributesForAnyStory"> <tr v-if="isLoadingAttributesForAnyStory && (!sortedUserstories || sortedUserstories.length > 0)">
<td :colspan="tableHeaders.length">Загружаем данные карточек...</td> <td :colspan="tableHeaders.length">Загружаем данные карточек...</td>
</tr> </tr>
</tbody> </tbody>
@@ -47,6 +50,9 @@ const dataStore = useDataStore();
const isLoadingInitialData = ref(true); const isLoadingInitialData = ref(true);
const isLoadingAttributesForAnyStory = ref(false); const isLoadingAttributesForAnyStory = ref(false);
const sortKey = ref<string | null>(null);
const sortOrder = ref<"asc" | "desc">("asc");
const tableHeaders = computed<TableHeader[]>(() => { const tableHeaders = computed<TableHeader[]>(() => {
const headers: TableHeader[] = [ const headers: TableHeader[] = [
{ key: "id", label: "ID", isAttribute: false }, { key: "id", label: "ID", isAttribute: false },
@@ -60,7 +66,7 @@ const tableHeaders = computed<TableHeader[]>(() => {
const sortedFields = [...fields].sort((a, b) => a.order - b.order); const sortedFields = [...fields].sort((a, b) => a.order - b.order);
sortedFields.forEach((field) => { sortedFields.forEach((field) => {
headers.push({ headers.push({
key: field.name, key: `attr_${field.id}`, // Ensure unique key for attributes
label: field.name, label: field.name,
isAttribute: true, isAttribute: true,
attributeId: field.id, attributeId: field.id,
@@ -74,22 +80,80 @@ const userstoriesForProject = computed(() => {
return dataStore.userstoriesMap.get(props.projectData.id); return dataStore.userstoriesMap.get(props.projectData.id);
}); });
const sortedUserstories = computed(() => {
if (!userstoriesForProject.value) {
return [];
}
if (!sortKey.value) {
return [...userstoriesForProject.value];
}
const sorted = [...userstoriesForProject.value];
const currentSortKeyVal = sortKey.value;
const currentSortOrderVal = sortOrder.value;
const headerToSortBy = tableHeaders.value.find((h) => h.key === currentSortKeyVal);
if (!headerToSortBy) {
return userstoriesForProject.value;
}
sorted.sort((a, b) => {
let valA_raw = getCellValue(a, headerToSortBy);
let valB_raw = getCellValue(b, headerToSortBy);
if (valA_raw === "...") valA_raw = "";
if (valB_raw === "...") valB_raw = "";
const valA_is_null_or_undefined = valA_raw === null || valA_raw === undefined;
const valB_is_null_or_undefined = valB_raw === null || valB_raw === undefined;
let comparisonResult = 0;
if (typeof valA_raw === "number" && typeof valB_raw === "number") {
comparisonResult = valA_raw - valB_raw;
} else {
const strA = valA_is_null_or_undefined ? "" : String(valA_raw).toLowerCase();
const strB = valB_is_null_or_undefined ? "" : String(valB_raw).toLowerCase();
if (strA < strB) {
comparisonResult = -1;
} else if (strA > strB) {
comparisonResult = 1;
}
}
return currentSortOrderVal === "asc" ? comparisonResult : -comparisonResult;
});
return sorted;
});
watch( watch(
userstoriesForProject, userstoriesForProject,
async (newUserstories) => { async (newUserstories) => {
if (newUserstories && newUserstories.length > 0) { if (newUserstories && newUserstories.length > 0) {
const storiesWithoutAttributes = newUserstories.filter((us) => !dataStore.userstoryAttributesMap.has(us.id));
if (storiesWithoutAttributes.length > 0) {
isLoadingAttributesForAnyStory.value = true; isLoadingAttributesForAnyStory.value = true;
const attributePromises = newUserstories const attributePromises = storiesWithoutAttributes.map((us) => dataStore.fetchUserstoryAttributes(us.id));
.filter((us) => !dataStore.userstoryAttributesMap.has(us.id))
.map((us) => dataStore.fetchUserstoryAttributes(us.id));
if (attributePromises.length > 0) { try {
await Promise.all(attributePromises); await Promise.all(attributePromises);
} catch (error) {
console.error(`Error loading attributes for project ${props.projectData.id}:`, error);
} finally {
const stillLoading = newUserstories.some(
(us) => !dataStore.userstoryAttributesMap.has(us.id) && storiesWithoutAttributes.find((s) => s.id === us.id),
);
isLoadingAttributesForAnyStory.value = stillLoading;
} }
} else {
isLoadingAttributesForAnyStory.value = false;
}
} else {
isLoadingAttributesForAnyStory.value = false; isLoadingAttributesForAnyStory.value = false;
} }
}, },
{ immediate: false }, { immediate: true, deep: true },
); );
onMounted(async () => { onMounted(async () => {
@@ -103,17 +167,28 @@ onMounted(async () => {
} }
}); });
function getCellValue(userstory: Userstory, header: TableHeader): string | number | UserstoryStatusInfo | null { function handleSort(headerKey: string) {
if (sortKey.value === headerKey) {
sortOrder.value = sortOrder.value === "asc" ? "desc" : "asc";
} else {
sortKey.value = headerKey;
sortOrder.value = "asc";
}
}
function getCellValue(userstory: Userstory, header: TableHeader): string | number | null {
if (!header.isAttribute) { if (!header.isAttribute) {
if (header.key === "status") { if (header.key === "status") {
return userstory.status_extra_info?.name || userstory.status.toString(); return userstory.status_extra_info?.name || userstory.status?.toString() || "";
} }
const value = userstory[header.key as keyof Userstory]; const value = userstory[header.key as keyof Userstory];
if (value === null) return null; if (value === null) return null;
if (typeof value === "string" || typeof value === "number") return value; if (typeof value === "string" || typeof value === "number") return value;
// Если значение undefined, ?? "" превратит его в пустую строку.
// Если это объект (например, UserstoryStatusInfo) или boolean, его нужно преобразовать в строку.
if (value === undefined) return ""; if (value === undefined) return "";
return String(value); return String(value); // Преобразует UserstoryStatusInfo, boolean и другие объекты в строку
} else { } else {
if (header.attributeId === undefined) return "N/A (no attr ID)"; if (header.attributeId === undefined) return "N/A (no attr ID)";
@@ -123,14 +198,15 @@ function getCellValue(userstory: Userstory, header: TableHeader): string | numbe
if (attrValue === null) return null; if (attrValue === null) return null;
if (typeof attrValue === "string" || typeof attrValue === "number") return attrValue; if (typeof attrValue === "string" || typeof attrValue === "number") return attrValue;
// Аналогично для атрибутов
if (attrValue === undefined) return ""; if (attrValue === undefined) return "";
return String(attrValue); return String(attrValue); // Преобразует boolean, object в строку
} }
if (isLoadingAttributesForAnyStory.value && !dataStore.userstoryAttributesMap.has(userstory.id)) { if (isLoadingAttributesForAnyStory.value && !dataStore.userstoryAttributesMap.has(userstory.id)) {
return "..."; return "...";
} }
return ""; return ""; // Если атрибутов нет и не идет загрузка, вернуть пустую строку
} }
} }
</script> </script>
@@ -151,6 +227,6 @@ table thead tr th {
font-weight: bold; font-weight: bold;
} }
table thead tr th:hover { table thead tr th:hover {
background-color: var(--vt-c-black-soft); background-color: var(--color-background-mute);
} }
</style> </style>