Compare commits
1 Commits
master
...
be9c056ada
| Author | SHA1 | Date | |
|---|---|---|---|
| be9c056ada |
@@ -4,23 +4,20 @@
|
|||||||
<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="sortedUserstories" class="userstory-table-container">
|
<div v-else-if="userstoriesForProject" class="userstory-table-container">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th v-for="header in tableHeaders" :key="header.key" @click="handleSort(header.key)" style="cursor: pointer">
|
<th v-for="header in tableHeaders" :key="header.key">{{ header.label }}</th>
|
||||||
{{ header.label }}
|
|
||||||
<span v-if="sortKey === header.key">{{ sortOrder === "asc" ? "▲" : "▼" }}</span>
|
|
||||||
</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="userstory in sortedUserstories" :key="userstory.id">
|
<tr v-for="userstory in userstoriesForProject" :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 && (!sortedUserstories || sortedUserstories.length > 0)">
|
<tr v-if="isLoadingAttributesForAnyStory">
|
||||||
<td :colspan="tableHeaders.length">Загружаем данные карточек...</td>
|
<td :colspan="tableHeaders.length">Загружаем данные карточек...</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -50,9 +47,6 @@ 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 },
|
||||||
@@ -62,10 +56,11 @@ const tableHeaders = computed<TableHeader[]>(() => {
|
|||||||
|
|
||||||
const fields = dataStore.projectFieldsMap.get(props.projectData.id);
|
const fields = dataStore.projectFieldsMap.get(props.projectData.id);
|
||||||
if (fields) {
|
if (fields) {
|
||||||
|
// Сортируем поля, возможно не нужно
|
||||||
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: `attr_${field.id}`,
|
key: field.name,
|
||||||
label: field.name,
|
label: field.name,
|
||||||
isAttribute: true,
|
isAttribute: true,
|
||||||
attributeId: field.id,
|
attributeId: field.id,
|
||||||
@@ -79,80 +74,22 @@ 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 = storiesWithoutAttributes.map((us) => dataStore.fetchUserstoryAttributes(us.id));
|
const attributePromises = newUserstories
|
||||||
|
.filter((us) => !dataStore.userstoryAttributesMap.has(us.id))
|
||||||
|
.map((us) => dataStore.fetchUserstoryAttributes(us.id));
|
||||||
|
|
||||||
try {
|
if (attributePromises.length > 0) {
|
||||||
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: true, deep: true },
|
{ immediate: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@@ -166,35 +103,21 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleSort(headerKey: string) {
|
function getCellValue(userstory: Userstory, header: TableHeader): string | number | UserstoryStatusInfo | null {
|
||||||
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];
|
return userstory[header.key as keyof Userstory] ?? "";
|
||||||
return value ?? "";
|
|
||||||
} else {
|
} else {
|
||||||
if (header.attributeId === undefined) return "N/A (no attr ID)";
|
if (header.attributeId === undefined) return "N/A (no attr ID)";
|
||||||
|
|
||||||
const attributes = dataStore.userstoryAttributesMap.get(userstory.id);
|
const attributes = dataStore.userstoryAttributesMap.get(userstory.id);
|
||||||
if (attributes) {
|
if (attributes) {
|
||||||
// Ключи для кастомных полей приходят как строки
|
// Ключи для кастомных полей приходят как строки
|
||||||
const attrValue = attributes[header.attributeId.toString()];
|
return attributes[header.attributeId.toString()] ?? "";
|
||||||
return attrValue ?? "";
|
|
||||||
}
|
}
|
||||||
if (isLoadingAttributesForAnyStory.value && !dataStore.userstoryAttributesMap.has(userstory.id)) {
|
return isLoadingAttributesForAnyStory.value ? "..." : "";
|
||||||
return "...";
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -213,9 +136,5 @@ function getCellValue(userstory: Userstory, header: TableHeader): string | numbe
|
|||||||
}
|
}
|
||||||
table thead tr th {
|
table thead tr th {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
/* cursor: pointer; is added inline for now */
|
|
||||||
}
|
|
||||||
table thead tr th:hover {
|
|
||||||
background-color: #f2f2f2;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user