feat(DiaryView): implement grouped plan table for enhanced activity display
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 42s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 42s
- Added a new grouped plan table view in the DiaryView component to display activities organized by groups. - Introduced computed properties `showGroupedPlanTable` and `groupedPlanRows` to manage the display logic and data structure for grouped activities. - Enhanced the template to conditionally render the grouped view, improving user experience and clarity in activity management.
This commit is contained in:
@@ -412,7 +412,12 @@
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<tr v-if="showGroupedPlanTable">
|
||||
<th>{{ $t('diary.startTime') }}</th>
|
||||
<th>Gemeinsam</th>
|
||||
<th v-for="group in groups" :key="`group-col-${group.id}`">{{ group.name }}</th>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<th></th> <!-- Neue Spalte für Drag-Handle -->
|
||||
<th>{{ $t('diary.startTime') }}</th>
|
||||
<th>{{ $t('diary.activityOrTimeblock') }}</th>
|
||||
@@ -421,7 +426,54 @@
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody ref="sortableList">
|
||||
<tbody v-if="showGroupedPlanTable">
|
||||
<tr v-for="row in groupedPlanRows" :key="row.key">
|
||||
<td>{{ formatDisplayTime(row.startTime) }}</td>
|
||||
<td>
|
||||
<div v-if="row.sharedItems.length" class="plan-cell-stack">
|
||||
<div v-for="item in row.sharedItems" :key="`shared-${item.id}`" class="plan-cell-item">
|
||||
<div class="plan-activity-main">
|
||||
<span @click="startActivityEdit(item)" class="clickable activity-label">{{ getPlanItemDisplayLabel(item) }}</span>
|
||||
<span v-if="!isStructuralPlanItem(item)" class="plan-status-badge" :class="`plan-status-badge-${getPlanItemStatus(item).tone}`">
|
||||
{{ getPlanItemStatus(item).label }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="plan-row-actions">
|
||||
<button @click="startActivityEdit(item)" class="plan-row-action-button">{{ $t('common.edit') }}</button>
|
||||
<button v-if="!isStructuralPlanItem(item)" @click="toggleActivityMembers(item)" class="plan-row-action-button">{{ $t('diary.assignShort') }}</button>
|
||||
<button @click="removePlanItem(item.id)" class="plan-row-action-button plan-row-action-button-danger">{{ $t('common.delete') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span v-else class="plan-row-muted">-</span>
|
||||
</td>
|
||||
<td v-for="group in groups" :key="`row-${row.key}-group-${group.id}`">
|
||||
<div v-if="(row.groupItems[group.id] || []).length" class="plan-cell-stack">
|
||||
<div v-for="item in (row.groupItems[group.id] || [])" :key="`group-item-${item.id}`" class="plan-cell-item">
|
||||
<div class="plan-activity-main">
|
||||
<span @click="startActivityEdit(item)" class="clickable activity-label">{{ getPlanItemDisplayLabel(item) }}</span>
|
||||
<span v-if="!isStructuralPlanItem(item)" class="plan-status-badge" :class="`plan-status-badge-${getPlanItemStatus(item).tone}`">
|
||||
{{ getPlanItemStatus(item).label }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="plan-row-actions">
|
||||
<button @click="startActivityEdit(item)" class="plan-row-action-button">{{ $t('common.edit') }}</button>
|
||||
<button v-if="!isStructuralPlanItem(item)" @click="toggleActivityMembers(item)" class="plan-row-action-button">{{ $t('diary.assignShort') }}</button>
|
||||
<button @click="removePlanItem(item.id)" class="plan-row-action-button plan-row-action-button-danger">{{ $t('common.delete') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span v-else class="plan-row-muted">-</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ calculateNextTime }}</td>
|
||||
<td :colspan="groups.length + 1">
|
||||
<span class="plan-add-hint">{{ $t('diary.planAddHint') }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody v-else ref="sortableList">
|
||||
<template v-for="(item, index) in filteredTrainingPlan" :key="item.id">
|
||||
<tr :class="{ 'plan-timeblock-row': item.isTimeblock }" class="plan-sortable-row" :data-plan-id="item.id">
|
||||
<td class="drag-handle" style="cursor: move;">☰</td> <!-- Drag-Handle -->
|
||||
@@ -1047,6 +1099,71 @@ export default {
|
||||
standalonePlanItemCount() {
|
||||
return (this.trainingPlan || []).filter(item => item && !item.isTimeblock).length;
|
||||
},
|
||||
showGroupedPlanTable() {
|
||||
return this.planGroupFilter === '__all__' && Array.isArray(this.groups) && this.groups.length > 0;
|
||||
},
|
||||
groupedPlanRows() {
|
||||
if (!this.showGroupedPlanTable) return [];
|
||||
const rowsByKey = new Map();
|
||||
const toMinutes = (timeValue) => {
|
||||
if (!timeValue || typeof timeValue !== 'string' || !timeValue.includes(':')) {
|
||||
return Number.MAX_SAFE_INTEGER;
|
||||
}
|
||||
const [h, m] = timeValue.split(':').map((part) => parseInt(part, 10));
|
||||
if (!Number.isFinite(h) || !Number.isFinite(m)) {
|
||||
return Number.MAX_SAFE_INTEGER;
|
||||
}
|
||||
return (h * 60) + m;
|
||||
};
|
||||
|
||||
for (const item of (this.trainingPlan || [])) {
|
||||
if (!item || item.isTimeblock) continue;
|
||||
const key = item.startTime || `unknown-${item.id}`;
|
||||
if (!rowsByKey.has(key)) {
|
||||
rowsByKey.set(key, {
|
||||
key,
|
||||
startTime: item.startTime || '',
|
||||
sharedItems: [],
|
||||
groupItems: {}
|
||||
});
|
||||
}
|
||||
const row = rowsByKey.get(key);
|
||||
const groupId = Number(item.groupId);
|
||||
if (Number.isFinite(groupId) && groupId > 0) {
|
||||
if (!Array.isArray(row.groupItems[groupId])) {
|
||||
row.groupItems[groupId] = [];
|
||||
}
|
||||
row.groupItems[groupId].push(item);
|
||||
} else {
|
||||
row.sharedItems.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
const sortItems = (items) => items.slice().sort((a, b) => {
|
||||
const orderA = Number.isFinite(Number(a?.orderId)) ? Number(a.orderId) : Number.MAX_SAFE_INTEGER;
|
||||
const orderB = Number.isFinite(Number(b?.orderId)) ? Number(b.orderId) : Number.MAX_SAFE_INTEGER;
|
||||
if (orderA !== orderB) return orderA - orderB;
|
||||
return Number(a?.id || 0) - Number(b?.id || 0);
|
||||
});
|
||||
|
||||
const rows = Array.from(rowsByKey.values()).map((row) => {
|
||||
row.sharedItems = sortItems(row.sharedItems);
|
||||
for (const group of this.groups || []) {
|
||||
const groupId = Number(group.id);
|
||||
if (Array.isArray(row.groupItems[groupId])) {
|
||||
row.groupItems[groupId] = sortItems(row.groupItems[groupId]);
|
||||
}
|
||||
}
|
||||
return row;
|
||||
});
|
||||
|
||||
return rows.sort((a, b) => {
|
||||
const startA = toMinutes(a?.startTime);
|
||||
const startB = toMinutes(b?.startTime);
|
||||
if (startA !== startB) return startA - startB;
|
||||
return String(a?.key || '').localeCompare(String(b?.key || ''));
|
||||
});
|
||||
},
|
||||
filteredTrainingPlan() {
|
||||
const allItems = Array.isArray(this.trainingPlan) ? this.trainingPlan : [];
|
||||
const toMinutes = (timeValue) => {
|
||||
@@ -1541,6 +1658,7 @@ export default {
|
||||
|
||||
initializeSortable() {
|
||||
const el = this.$refs.sortableList;
|
||||
if (!el) return;
|
||||
Sortable.create(el, {
|
||||
draggable: '.plan-sortable-row',
|
||||
handle: ".drag-handle",
|
||||
@@ -2836,6 +2954,17 @@ export default {
|
||||
const value = String(timeValue);
|
||||
return value.length >= 5 ? value.slice(0, 5) : value;
|
||||
},
|
||||
getPlanItemDisplayLabel(item) {
|
||||
if (!item) return '';
|
||||
const predefined = item.predefinedActivity || item.groupPredefinedActivity;
|
||||
if (predefined?.code && predefined.code.trim() !== '') {
|
||||
return predefined.code;
|
||||
}
|
||||
if (predefined?.name) {
|
||||
return predefined.name;
|
||||
}
|
||||
return item.activity || '';
|
||||
},
|
||||
editGroup(groupId) {
|
||||
this.editingGroupId = groupId;
|
||||
},
|
||||
@@ -5297,6 +5426,22 @@ img {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.plan-cell-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.plan-cell-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
padding: 0.35rem 0.4rem;
|
||||
border: 1px solid #e4edf2;
|
||||
border-radius: 8px;
|
||||
background: #f9fcfe;
|
||||
}
|
||||
|
||||
.plan-row-action-button {
|
||||
border: 1px solid #cfdbe3;
|
||||
background: #f3f7fa;
|
||||
|
||||
Reference in New Issue
Block a user