@camp2gether/c2g-ui 0.0.7
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/README.md +95 -0
- package/charts/index.d.ts +199 -0
- package/fesm2022/camp2gether-c2g-ui-beach-animation-ipi3OoKW.mjs +54156 -0
- package/fesm2022/camp2gether-c2g-ui-beach-animation-ipi3OoKW.mjs.map +1 -0
- package/fesm2022/camp2gether-c2g-ui-camping-animation-DY6XWXyF.mjs +35807 -0
- package/fesm2022/camp2gether-c2g-ui-camping-animation-DY6XWXyF.mjs.map +1 -0
- package/fesm2022/camp2gether-c2g-ui-car-animation-DnDp7WfG.mjs +45189 -0
- package/fesm2022/camp2gether-c2g-ui-car-animation-DnDp7WfG.mjs.map +1 -0
- package/fesm2022/camp2gether-c2g-ui-car-driving-landscape-animation-CawNeMKD.mjs +43833 -0
- package/fesm2022/camp2gether-c2g-ui-car-driving-landscape-animation-CawNeMKD.mjs.map +1 -0
- package/fesm2022/camp2gether-c2g-ui-cat-love-animation-ewC7fZyY.mjs +30789 -0
- package/fesm2022/camp2gether-c2g-ui-cat-love-animation-ewC7fZyY.mjs.map +1 -0
- package/fesm2022/camp2gether-c2g-ui-charts.mjs +404 -0
- package/fesm2022/camp2gether-c2g-ui-charts.mjs.map +1 -0
- package/fesm2022/camp2gether-c2g-ui-checklist-animation-DqUkcLqI.mjs +19868 -0
- package/fesm2022/camp2gether-c2g-ui-checklist-animation-DqUkcLqI.mjs.map +1 -0
- package/fesm2022/camp2gether-c2g-ui-coffee-time-animation-DQilaE0A.mjs +6816 -0
- package/fesm2022/camp2gether-c2g-ui-coffee-time-animation-DQilaE0A.mjs.map +1 -0
- package/fesm2022/camp2gether-c2g-ui-error-404-pdjg-EHb.mjs +49742 -0
- package/fesm2022/camp2gether-c2g-ui-error-404-pdjg-EHb.mjs.map +1 -0
- package/fesm2022/camp2gether-c2g-ui-fishing-animation-DwE3IF-V.mjs +38941 -0
- package/fesm2022/camp2gether-c2g-ui-fishing-animation-DwE3IF-V.mjs.map +1 -0
- package/fesm2022/camp2gether-c2g-ui-layout.mjs +768 -0
- package/fesm2022/camp2gether-c2g-ui-layout.mjs.map +1 -0
- package/fesm2022/camp2gether-c2g-ui-maps.mjs +223 -0
- package/fesm2022/camp2gether-c2g-ui-maps.mjs.map +1 -0
- package/fesm2022/camp2gether-c2g-ui-mountain-search-animation-TebM1gS4.mjs +69245 -0
- package/fesm2022/camp2gether-c2g-ui-mountain-search-animation-TebM1gS4.mjs.map +1 -0
- package/fesm2022/camp2gether-c2g-ui-planning-animation-D8QSsZk6.mjs +28330 -0
- package/fesm2022/camp2gether-c2g-ui-planning-animation-D8QSsZk6.mjs.map +1 -0
- package/fesm2022/camp2gether-c2g-ui-presets.mjs +2855 -0
- package/fesm2022/camp2gether-c2g-ui-presets.mjs.map +1 -0
- package/fesm2022/camp2gether-c2g-ui-share-animation-qgqs-k59.mjs +59129 -0
- package/fesm2022/camp2gether-c2g-ui-share-animation-qgqs-k59.mjs.map +1 -0
- package/fesm2022/camp2gether-c2g-ui-summer-camp-animation-DPzirVNH.mjs +89317 -0
- package/fesm2022/camp2gether-c2g-ui-summer-camp-animation-DPzirVNH.mjs.map +1 -0
- package/fesm2022/camp2gether-c2g-ui-theme.mjs +479 -0
- package/fesm2022/camp2gether-c2g-ui-theme.mjs.map +1 -0
- package/fesm2022/camp2gether-c2g-ui-thinking-animation--X3er_pf.mjs +27929 -0
- package/fesm2022/camp2gether-c2g-ui-thinking-animation--X3er_pf.mjs.map +1 -0
- package/fesm2022/camp2gether-c2g-ui-walking-avocado-animation-CQMU2C9-.mjs +4064 -0
- package/fesm2022/camp2gether-c2g-ui-walking-avocado-animation-CQMU2C9-.mjs.map +1 -0
- package/fesm2022/camp2gether-c2g-ui-walking-orange-animation-CTJniCsF.mjs +3113 -0
- package/fesm2022/camp2gether-c2g-ui-walking-orange-animation-CTJniCsF.mjs.map +1 -0
- package/fesm2022/camp2gether-c2g-ui-weather-partly-cloudy-animation-Cnw3W4cS.mjs +1731 -0
- package/fesm2022/camp2gether-c2g-ui-weather-partly-cloudy-animation-Cnw3W4cS.mjs.map +1 -0
- package/fesm2022/camp2gether-c2g-ui.mjs +2099 -0
- package/fesm2022/camp2gether-c2g-ui.mjs.map +1 -0
- package/index.d.ts +578 -0
- package/layout/index.d.ts +443 -0
- package/maps/index.d.ts +62 -0
- package/package.json +51 -0
- package/presets/index.d.ts +1437 -0
- package/src/lib/styles/design-tokens.css +153 -0
- package/src/lib/styles/themes.scss +346 -0
- package/theme/index.d.ts +63 -0
|
@@ -0,0 +1,2855 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, output, signal, computed, ChangeDetectionStrategy, Component, effect, HostListener } from '@angular/core';
|
|
3
|
+
import * as i1 from '@angular/material/icon';
|
|
4
|
+
import { MatIconModule } from '@angular/material/icon';
|
|
5
|
+
import { AvatarComponent, BadgeComponent, ButtonComponent, RadioGroupComponent, CheckboxComponent, SelectComponent, InputComponent, TextareaComponent } from '@humbeldore/c2g-ui';
|
|
6
|
+
import * as i1$2 from '@angular/common';
|
|
7
|
+
import { CommonModule } from '@angular/common';
|
|
8
|
+
import * as i1$1 from '@angular/forms';
|
|
9
|
+
import { FormsModule } from '@angular/forms';
|
|
10
|
+
import { CdkMenu, CdkMenuItem, CdkMenuTrigger } from '@angular/cdk/menu';
|
|
11
|
+
import { LottieComponent } from 'ngx-lottie';
|
|
12
|
+
|
|
13
|
+
const DEFAULT_PACKING_LIST_LABEL_KEYS = {
|
|
14
|
+
searchPlaceholderKey: 'packingList.search.placeholder',
|
|
15
|
+
essentialsOnlyKey: 'packingList.filters.essentialsOnly',
|
|
16
|
+
showAllVisibilityKey: 'packingList.filters.visibility.all',
|
|
17
|
+
sharedVisibilityKey: 'packingList.filters.visibility.shared',
|
|
18
|
+
personalVisibilityKey: 'packingList.filters.visibility.personal',
|
|
19
|
+
privateVisibilityKey: 'packingList.filters.visibility.private',
|
|
20
|
+
privateSectionTitleKey: 'packingList.private.title',
|
|
21
|
+
emptyStateKey: 'packingList.empty',
|
|
22
|
+
categoryStatsKey: 'packingList.category.stats',
|
|
23
|
+
deleteConfirmKey: 'packingList.delete.confirm',
|
|
24
|
+
addItemKey: 'packingList.addItem',
|
|
25
|
+
createItemKey: 'packingList.createItem',
|
|
26
|
+
itemNameKey: 'packingList.itemName',
|
|
27
|
+
categoryKey: 'packingList.category',
|
|
28
|
+
visibilityKey: 'packingList.visibility',
|
|
29
|
+
essentialKey: 'packingList.essential',
|
|
30
|
+
quantityKey: 'packingList.quantity',
|
|
31
|
+
weightKey: 'packingList.weight',
|
|
32
|
+
volumeKey: 'packingList.volume',
|
|
33
|
+
hintKey: 'packingList.hint',
|
|
34
|
+
cancelKey: 'packingList.cancel',
|
|
35
|
+
createKey: 'packingList.create'
|
|
36
|
+
};
|
|
37
|
+
const DEFAULT_PACKING_LIST_CONFIG = {
|
|
38
|
+
features: {
|
|
39
|
+
search: true,
|
|
40
|
+
filters: true,
|
|
41
|
+
stats: true,
|
|
42
|
+
create: false,
|
|
43
|
+
assignments: true,
|
|
44
|
+
privateSection: false,
|
|
45
|
+
expandAllOnLoad: false,
|
|
46
|
+
},
|
|
47
|
+
defaultExpandedCount: 3,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const C2G_PACKING_CATEGORY_INFO = {
|
|
51
|
+
shelter: { key: 'shelter', labelKey: 'packingList.categories.shelter', label: 'Unterkunft', icon: 'home' },
|
|
52
|
+
sleeping: { key: 'sleeping', labelKey: 'packingList.categories.sleeping', label: 'Schlafen', icon: 'bed' },
|
|
53
|
+
cookingEating: { key: 'cookingEating', labelKey: 'packingList.categories.cookingEating', label: 'Kochen & Essen', icon: 'restaurant' },
|
|
54
|
+
clothing: { key: 'clothing', labelKey: 'packingList.categories.clothing', label: 'Kleidung', icon: 'checkroom' },
|
|
55
|
+
navigation: { key: 'navigation', labelKey: 'packingList.categories.navigation', label: 'Navigation', icon: 'explore' },
|
|
56
|
+
lighting: { key: 'lighting', labelKey: 'packingList.categories.lighting', label: 'Beleuchtung', icon: 'light_mode' },
|
|
57
|
+
safety: { key: 'safety', labelKey: 'packingList.categories.safety', label: 'Sicherheit', icon: 'medical_services' },
|
|
58
|
+
tools: { key: 'tools', labelKey: 'packingList.categories.tools', label: 'Werkzeug', icon: 'build' },
|
|
59
|
+
hygiene: { key: 'hygiene', labelKey: 'packingList.categories.hygiene', label: 'Hygiene', icon: 'wash' },
|
|
60
|
+
furniture: { key: 'furniture', labelKey: 'packingList.categories.furniture', label: 'Möbel & Ausstattung', icon: 'chair' },
|
|
61
|
+
hydration: { key: 'hydration', labelKey: 'packingList.categories.hydration', label: 'Getränke', icon: 'water_drop' },
|
|
62
|
+
mobility: { key: 'mobility', labelKey: 'packingList.categories.mobility', label: 'Transport', icon: 'backpack' },
|
|
63
|
+
activity: { key: 'activity', labelKey: 'packingList.categories.activity', label: 'Aktivitäten', icon: 'sports' },
|
|
64
|
+
documents: { key: 'documents', labelKey: 'packingList.categories.documents', label: 'Dokumente', icon: 'description' },
|
|
65
|
+
other: { key: 'other', labelKey: 'packingList.categories.other', label: 'Sonstiges', icon: 'category' }
|
|
66
|
+
};
|
|
67
|
+
function resolvePackingCategory(categoryKey) {
|
|
68
|
+
return (C2G_PACKING_CATEGORY_INFO[categoryKey] ?? {
|
|
69
|
+
key: categoryKey,
|
|
70
|
+
labelKey: `packingList.categories.${categoryKey}`,
|
|
71
|
+
icon: 'inventory_2'
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
class PackingListItemComponent {
|
|
76
|
+
item = input.required(...(ngDevMode ? [{ debugName: "item" }] : []));
|
|
77
|
+
members = input.required(...(ngDevMode ? [{ debugName: "members" }] : []));
|
|
78
|
+
currentUserId = input.required(...(ngDevMode ? [{ debugName: "currentUserId" }] : []));
|
|
79
|
+
permissions = input.required(...(ngDevMode ? [{ debugName: "permissions" }] : []));
|
|
80
|
+
selectedMemberIds = input([], ...(ngDevMode ? [{ debugName: "selectedMemberIds" }] : []));
|
|
81
|
+
itemChecked = output();
|
|
82
|
+
itemAssigned = output();
|
|
83
|
+
itemDeleted = output();
|
|
84
|
+
itemEditRequested = output();
|
|
85
|
+
personalItemToggled = output();
|
|
86
|
+
memberOverlayRequested = output();
|
|
87
|
+
overlayOpen = signal(false, ...(ngDevMode ? [{ debugName: "overlayOpen" }] : []));
|
|
88
|
+
overlaySearch = signal('', ...(ngDevMode ? [{ debugName: "overlaySearch" }] : []));
|
|
89
|
+
ctaMenuOpen = signal(false, ...(ngDevMode ? [{ debugName: "ctaMenuOpen" }] : []));
|
|
90
|
+
quantityPickerOpen = signal(false, ...(ngDevMode ? [{ debugName: "quantityPickerOpen" }] : []));
|
|
91
|
+
quantityPickerValue = signal(1, ...(ngDevMode ? [{ debugName: "quantityPickerValue" }] : []));
|
|
92
|
+
delegateDialogOpen = signal(false, ...(ngDevMode ? [{ debugName: "delegateDialogOpen" }] : []));
|
|
93
|
+
delegateMemberId = signal(null, ...(ngDevMode ? [{ debugName: "delegateMemberId" }] : []));
|
|
94
|
+
delegateQuantity = signal(1, ...(ngDevMode ? [{ debugName: "delegateQuantity" }] : []));
|
|
95
|
+
avatarDialogOpen = signal(false, ...(ngDevMode ? [{ debugName: "avatarDialogOpen" }] : []));
|
|
96
|
+
avatarDialogMemberId = signal(null, ...(ngDevMode ? [{ debugName: "avatarDialogMemberId" }] : []));
|
|
97
|
+
avatarDialogQuantity = signal(1, ...(ngDevMode ? [{ debugName: "avatarDialogQuantity" }] : []));
|
|
98
|
+
personalAssignDialogOpen = signal(false, ...(ngDevMode ? [{ debugName: "personalAssignDialogOpen" }] : []));
|
|
99
|
+
personalAssignSearch = signal('', ...(ngDevMode ? [{ debugName: "personalAssignSearch" }] : []));
|
|
100
|
+
personalAssignMemberId = signal(null, ...(ngDevMode ? [{ debugName: "personalAssignMemberId" }] : []));
|
|
101
|
+
applicableMembers = computed(() => {
|
|
102
|
+
const item = this.item();
|
|
103
|
+
if (item.visibility === 'personal') {
|
|
104
|
+
return this.members().filter(m => m.type !== 'pet');
|
|
105
|
+
}
|
|
106
|
+
return this.members().filter(m => m.type !== 'child' && m.type !== 'pet');
|
|
107
|
+
}, ...(ngDevMode ? [{ debugName: "applicableMembers" }] : []));
|
|
108
|
+
visibleMembers = computed(() => {
|
|
109
|
+
const selected = this.selectedMemberIds();
|
|
110
|
+
if (selected.length === 0) {
|
|
111
|
+
return this.applicableMembers();
|
|
112
|
+
}
|
|
113
|
+
const allowed = new Set(selected);
|
|
114
|
+
return this.applicableMembers().filter(member => allowed.has(member.id));
|
|
115
|
+
}, ...(ngDevMode ? [{ debugName: "visibleMembers" }] : []));
|
|
116
|
+
packedCount = computed(() => {
|
|
117
|
+
const assignments = this.item().assignments ?? [];
|
|
118
|
+
return assignments
|
|
119
|
+
.filter(a => a.status === 'packed' || a.status === 'confirmed')
|
|
120
|
+
.reduce((sum, a) => sum + Math.max(1, a.quantity ?? 1), 0);
|
|
121
|
+
}, ...(ngDevMode ? [{ debugName: "packedCount" }] : []));
|
|
122
|
+
isPacked = computed(() => this.packedCount() > 0 || !!this.item().confirmed, ...(ngDevMode ? [{ debugName: "isPacked" }] : []));
|
|
123
|
+
requiredQuantity = computed(() => Math.max(1, this.item().quantity ?? 1), ...(ngDevMode ? [{ debugName: "requiredQuantity" }] : []));
|
|
124
|
+
checkedMembers = computed(() => {
|
|
125
|
+
const currentUserId = this.currentUserId();
|
|
126
|
+
const checked = this.visibleMembers().filter(member => this.isMemberChecked(member.id));
|
|
127
|
+
return checked.sort((a, b) => {
|
|
128
|
+
if (a.id === currentUserId)
|
|
129
|
+
return -1;
|
|
130
|
+
if (b.id === currentUserId)
|
|
131
|
+
return 1;
|
|
132
|
+
return 0;
|
|
133
|
+
});
|
|
134
|
+
}, ...(ngDevMode ? [{ debugName: "checkedMembers" }] : []));
|
|
135
|
+
personalAssignedMembers = computed(() => {
|
|
136
|
+
if (this.item().visibility !== 'personal') {
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
const currentUserId = this.currentUserId();
|
|
140
|
+
const assigned = this.visibleMembers().filter(member => this.hasAnyAssignment(member.id));
|
|
141
|
+
return assigned.sort((a, b) => {
|
|
142
|
+
if (a.id === currentUserId)
|
|
143
|
+
return -1;
|
|
144
|
+
if (b.id === currentUserId)
|
|
145
|
+
return 1;
|
|
146
|
+
return 0;
|
|
147
|
+
});
|
|
148
|
+
}, ...(ngDevMode ? [{ debugName: "personalAssignedMembers" }] : []));
|
|
149
|
+
missingMembers = computed(() => this.visibleMembers().filter(member => !this.isMemberChecked(member.id)), ...(ngDevMode ? [{ debugName: "missingMembers" }] : []));
|
|
150
|
+
checkedPreview = computed(() => this.checkedMembers().slice(0, 3), ...(ngDevMode ? [{ debugName: "checkedPreview" }] : []));
|
|
151
|
+
hiddenCheckedCount = computed(() => Math.max(0, this.checkedMembers().length - this.checkedPreview().length), ...(ngDevMode ? [{ debugName: "hiddenCheckedCount" }] : []));
|
|
152
|
+
personalAssignedPreview = computed(() => this.personalAssignedMembers().slice(0, 3), ...(ngDevMode ? [{ debugName: "personalAssignedPreview" }] : []));
|
|
153
|
+
personalHiddenAssignedCount = computed(() => Math.max(0, this.personalAssignedMembers().length - this.personalAssignedPreview().length), ...(ngDevMode ? [{ debugName: "personalHiddenAssignedCount" }] : []));
|
|
154
|
+
missingPreview = computed(() => this.missingMembers().slice(0, 3), ...(ngDevMode ? [{ debugName: "missingPreview" }] : []));
|
|
155
|
+
hiddenMissingCount = computed(() => Math.max(0, this.missingMembers().length - this.missingPreview().length), ...(ngDevMode ? [{ debugName: "hiddenMissingCount" }] : []));
|
|
156
|
+
currentUserMember = computed(() => this.members().find(member => member.id === this.currentUserId()) ?? null, ...(ngDevMode ? [{ debugName: "currentUserMember" }] : []));
|
|
157
|
+
isItemOwner = computed(() => {
|
|
158
|
+
const ownerId = this.item().createdBy;
|
|
159
|
+
if (!ownerId) {
|
|
160
|
+
// Legacy items may not carry ownership metadata.
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
return ownerId === this.currentUserId();
|
|
164
|
+
}, ...(ngDevMode ? [{ debugName: "isItemOwner" }] : []));
|
|
165
|
+
canEditOrDeleteItem = computed(() => {
|
|
166
|
+
const permissions = this.permissions();
|
|
167
|
+
if (permissions.readOnly) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
const item = this.item();
|
|
171
|
+
if (item.visibility === 'shared') {
|
|
172
|
+
return permissions.isOrganizer || !!permissions.canMembersManageSharedItems || this.isItemOwner();
|
|
173
|
+
}
|
|
174
|
+
return permissions.isOrganizer || this.isItemOwner();
|
|
175
|
+
}, ...(ngDevMode ? [{ debugName: "canEditOrDeleteItem" }] : []));
|
|
176
|
+
canShowAssignCta = computed(() => {
|
|
177
|
+
const permissions = this.permissions();
|
|
178
|
+
if (permissions.readOnly) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
if (permissions.isSolo) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
return permissions.isOrganizer || !!permissions.canManageAssignments;
|
|
185
|
+
}, ...(ngDevMode ? [{ debugName: "canShowAssignCta" }] : []));
|
|
186
|
+
sharedAssignmentsCount = computed(() => {
|
|
187
|
+
const assignments = this.item().assignments ?? [];
|
|
188
|
+
return assignments
|
|
189
|
+
.filter(a => a.status === 'confirmed' || a.status === 'packed')
|
|
190
|
+
.reduce((sum, a) => sum + Math.max(1, a.quantity ?? 1), 0);
|
|
191
|
+
}, ...(ngDevMode ? [{ debugName: "sharedAssignmentsCount" }] : []));
|
|
192
|
+
sharedTotalAssignedCount = computed(() => {
|
|
193
|
+
const assignments = this.item().assignments ?? [];
|
|
194
|
+
return assignments.reduce((sum, a) => sum + Math.max(1, a.quantity ?? 1), 0);
|
|
195
|
+
}, ...(ngDevMode ? [{ debugName: "sharedTotalAssignedCount" }] : []));
|
|
196
|
+
sharedMissingSlots = computed(() => Math.max(0, this.requiredQuantity() - this.sharedTotalAssignedCount()), ...(ngDevMode ? [{ debugName: "sharedMissingSlots" }] : []));
|
|
197
|
+
selfAssignableMaxQuantity = computed(() => Math.max(1, this.sharedMissingSlots()), ...(ngDevMode ? [{ debugName: "selfAssignableMaxQuantity" }] : []));
|
|
198
|
+
delegateCandidates = computed(() => this.visibleMembers().filter(member => this.canAssignShared(member.id)), ...(ngDevMode ? [{ debugName: "delegateCandidates" }] : []));
|
|
199
|
+
selfAssignedToShared = computed(() => this.isSharedAssigned(this.currentUserId()), ...(ngDevMode ? [{ debugName: "selfAssignedToShared" }] : []));
|
|
200
|
+
isPlanner = computed(() => {
|
|
201
|
+
const p = this.permissions();
|
|
202
|
+
return p.isOrganizer || !!p.canManageAssignments;
|
|
203
|
+
}, ...(ngDevMode ? [{ debugName: "isPlanner" }] : []));
|
|
204
|
+
// Label for the "join / adjust" button on shared items
|
|
205
|
+
joinBtnLabel = computed(() => {
|
|
206
|
+
if (!this.selfAssignedToShared())
|
|
207
|
+
return '+ Eintragen';
|
|
208
|
+
return this.isPlanner() ? 'Menge anpassen' : 'Meine Menge anpassen';
|
|
209
|
+
}, ...(ngDevMode ? [{ debugName: "joinBtnLabel" }] : []));
|
|
210
|
+
// Context label inside the quantity picker
|
|
211
|
+
pickerLabel = computed(() => {
|
|
212
|
+
if (this.isPlanner())
|
|
213
|
+
return 'Wie viel wird mitgenommen?';
|
|
214
|
+
return 'Wie viel nimmst du mit?';
|
|
215
|
+
}, ...(ngDevMode ? [{ debugName: "pickerLabel" }] : []));
|
|
216
|
+
// Context label inside the delegate dialog
|
|
217
|
+
delegatePickerLabel = computed(() => 'Wer bringt es mit? Menge festlegen:', ...(ngDevMode ? [{ debugName: "delegatePickerLabel" }] : []));
|
|
218
|
+
canSelfAssignShared = computed(() => {
|
|
219
|
+
const currentUserId = this.currentUserId();
|
|
220
|
+
if (!this.canAssignShared(currentUserId)) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
if (this.selfAssignedToShared()) {
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
return this.sharedMissingSlots() > 0;
|
|
227
|
+
}, ...(ngDevMode ? [{ debugName: "canSelfAssignShared" }] : []));
|
|
228
|
+
selectedDelegateMember = computed(() => {
|
|
229
|
+
const memberId = this.delegateMemberId();
|
|
230
|
+
if (!memberId) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
return this.members().find(member => member.id === memberId) ?? null;
|
|
234
|
+
}, ...(ngDevMode ? [{ debugName: "selectedDelegateMember" }] : []));
|
|
235
|
+
canManagePersonalAssignments = computed(() => {
|
|
236
|
+
const permissions = this.permissions();
|
|
237
|
+
if (permissions.readOnly || permissions.isSolo) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
return permissions.isOrganizer || !!permissions.canManageAssignments || this.isItemOwner();
|
|
241
|
+
}, ...(ngDevMode ? [{ debugName: "canManagePersonalAssignments" }] : []));
|
|
242
|
+
personalAssignableMembers = computed(() => {
|
|
243
|
+
if (this.item().visibility !== 'personal' || !this.canManagePersonalAssignments()) {
|
|
244
|
+
return [];
|
|
245
|
+
}
|
|
246
|
+
return this.visibleMembers().filter(member => !this.hasAnyAssignment(member.id));
|
|
247
|
+
}, ...(ngDevMode ? [{ debugName: "personalAssignableMembers" }] : []));
|
|
248
|
+
filteredPersonalAssignableMembers = computed(() => {
|
|
249
|
+
const query = this.personalAssignSearch().trim().toLowerCase();
|
|
250
|
+
if (!query) {
|
|
251
|
+
return this.personalAssignableMembers();
|
|
252
|
+
}
|
|
253
|
+
return this.personalAssignableMembers().filter(member => {
|
|
254
|
+
const searchable = `${member.name} ${member.initials ?? ''}`.toLowerCase();
|
|
255
|
+
return searchable.includes(query);
|
|
256
|
+
});
|
|
257
|
+
}, ...(ngDevMode ? [{ debugName: "filteredPersonalAssignableMembers" }] : []));
|
|
258
|
+
selectedPersonalAssignMember = computed(() => {
|
|
259
|
+
const memberId = this.personalAssignMemberId();
|
|
260
|
+
if (!memberId) {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
return this.members().find(member => member.id === memberId) ?? null;
|
|
264
|
+
}, ...(ngDevMode ? [{ debugName: "selectedPersonalAssignMember" }] : []));
|
|
265
|
+
completionText = computed(() => {
|
|
266
|
+
if (this.item().visibility === 'shared') {
|
|
267
|
+
return `${this.sharedAssignmentsCount()}/${this.requiredQuantity()} bereit`;
|
|
268
|
+
}
|
|
269
|
+
if (this.item().visibility === 'personal') {
|
|
270
|
+
return `${this.checkedMembers().length}/${this.visibleMembers().length} bereit`;
|
|
271
|
+
}
|
|
272
|
+
return this.isPacked() ? 'bereit' : 'offen';
|
|
273
|
+
}, ...(ngDevMode ? [{ debugName: "completionText" }] : []));
|
|
274
|
+
progressNumerator = computed(() => {
|
|
275
|
+
if (this.item().visibility === 'shared') {
|
|
276
|
+
return this.sharedAssignmentsCount();
|
|
277
|
+
}
|
|
278
|
+
if (this.item().visibility === 'personal') {
|
|
279
|
+
return this.checkedMembers().length;
|
|
280
|
+
}
|
|
281
|
+
return this.isPacked() ? 1 : 0;
|
|
282
|
+
}, ...(ngDevMode ? [{ debugName: "progressNumerator" }] : []));
|
|
283
|
+
progressDenominator = computed(() => {
|
|
284
|
+
if (this.item().visibility === 'shared') {
|
|
285
|
+
return this.requiredQuantity();
|
|
286
|
+
}
|
|
287
|
+
if (this.item().visibility === 'personal') {
|
|
288
|
+
return Math.max(1, this.visibleMembers().length);
|
|
289
|
+
}
|
|
290
|
+
return 1;
|
|
291
|
+
}, ...(ngDevMode ? [{ debugName: "progressDenominator" }] : []));
|
|
292
|
+
progressRatioText = computed(() => `${this.progressNumerator()}/${this.progressDenominator()}`, ...(ngDevMode ? [{ debugName: "progressRatioText" }] : []));
|
|
293
|
+
// Badge shown top-right: "x/y" packed — only for group items
|
|
294
|
+
quantityBadge = computed(() => {
|
|
295
|
+
const vis = this.item().visibility;
|
|
296
|
+
if (this.permissions().isSolo || vis === 'private')
|
|
297
|
+
return null;
|
|
298
|
+
const n = this.progressNumerator();
|
|
299
|
+
const d = this.progressDenominator();
|
|
300
|
+
if (d <= 1 && vis === 'shared')
|
|
301
|
+
return null; // single shared item, dot is enough
|
|
302
|
+
return { value: `${n}/${d}`, tone: this.progressTone() };
|
|
303
|
+
}, ...(ngDevMode ? [{ debugName: "quantityBadge" }] : []));
|
|
304
|
+
progressPercentage = computed(() => {
|
|
305
|
+
const denominator = this.progressDenominator();
|
|
306
|
+
if (denominator <= 0) {
|
|
307
|
+
return 0;
|
|
308
|
+
}
|
|
309
|
+
return Math.min(100, Math.round((this.progressNumerator() / denominator) * 100));
|
|
310
|
+
}, ...(ngDevMode ? [{ debugName: "progressPercentage" }] : []));
|
|
311
|
+
progressTone = computed(() => {
|
|
312
|
+
const progress = this.progressPercentage();
|
|
313
|
+
if (progress === 100) {
|
|
314
|
+
return 'success';
|
|
315
|
+
}
|
|
316
|
+
if (progress >= 50) {
|
|
317
|
+
return 'warning';
|
|
318
|
+
}
|
|
319
|
+
return 'danger';
|
|
320
|
+
}, ...(ngDevMode ? [{ debugName: "progressTone" }] : []));
|
|
321
|
+
needsPackLabel = computed(() => {
|
|
322
|
+
if (this.item().visibility === 'shared') {
|
|
323
|
+
return `${this.sharedMissingSlots()} offen`;
|
|
324
|
+
}
|
|
325
|
+
return `${this.missingMembers().length} müssen packen`;
|
|
326
|
+
}, ...(ngDevMode ? [{ debugName: "needsPackLabel" }] : []));
|
|
327
|
+
overlayMembers = computed(() => this.visibleMembers().map(member => ({
|
|
328
|
+
memberId: member.id,
|
|
329
|
+
memberName: member.name,
|
|
330
|
+
initials: member.initials,
|
|
331
|
+
checked: this.isMemberChecked(member.id),
|
|
332
|
+
canToggle: this.item().visibility === 'shared'
|
|
333
|
+
? this.canAssignShared(member.id)
|
|
334
|
+
: this.canPackForMember(member.id)
|
|
335
|
+
})), ...(ngDevMode ? [{ debugName: "overlayMembers" }] : []));
|
|
336
|
+
filteredOverlayMembers = computed(() => {
|
|
337
|
+
const query = this.overlaySearch().trim().toLowerCase();
|
|
338
|
+
if (!query) {
|
|
339
|
+
return this.overlayMembers();
|
|
340
|
+
}
|
|
341
|
+
return this.overlayMembers().filter(member => {
|
|
342
|
+
const searchable = `${member.memberName} ${member.initials ?? ''}`.toLowerCase();
|
|
343
|
+
return searchable.includes(query);
|
|
344
|
+
});
|
|
345
|
+
}, ...(ngDevMode ? [{ debugName: "filteredOverlayMembers" }] : []));
|
|
346
|
+
personalStatusBadges = computed(() => {
|
|
347
|
+
if (this.item().visibility !== 'personal') {
|
|
348
|
+
return [];
|
|
349
|
+
}
|
|
350
|
+
const badges = [];
|
|
351
|
+
for (const assignment of this.item().assignments ?? []) {
|
|
352
|
+
const targetMember = this.members().find(m => m.id === assignment.memberId);
|
|
353
|
+
const targetName = targetMember?.name ?? assignment.memberName ?? assignment.memberId;
|
|
354
|
+
const sourceMember = assignment.packedBy ? this.members().find(m => m.id === assignment.packedBy) : null;
|
|
355
|
+
const sourceName = sourceMember?.name ?? assignment.packedByName ?? assignment.packedBy;
|
|
356
|
+
const isTakeover = !!assignment.packedBy && assignment.packedBy !== assignment.memberId;
|
|
357
|
+
badges.push({
|
|
358
|
+
key: `${assignment.memberId}:${assignment.packedBy ?? assignment.memberId}:${assignment.status}`,
|
|
359
|
+
text: isTakeover ? `${sourceName} -> ${targetName}` : targetName,
|
|
360
|
+
tone: assignment.status === 'assigned' ? 'warning' : 'success',
|
|
361
|
+
dotColor: this.memberRingColor(assignment.memberId)
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
return badges.sort((a, b) => {
|
|
365
|
+
const aIsCurrent = a.key.startsWith(`${this.currentUserId()}:`);
|
|
366
|
+
const bIsCurrent = b.key.startsWith(`${this.currentUserId()}:`);
|
|
367
|
+
if (aIsCurrent && !bIsCurrent)
|
|
368
|
+
return -1;
|
|
369
|
+
if (!aIsCurrent && bIsCurrent)
|
|
370
|
+
return 1;
|
|
371
|
+
return a.text.localeCompare(b.text);
|
|
372
|
+
});
|
|
373
|
+
}, ...(ngDevMode ? [{ debugName: "personalStatusBadges" }] : []));
|
|
374
|
+
sharedCountChips = computed(() => {
|
|
375
|
+
if (this.item().visibility !== 'shared') {
|
|
376
|
+
return [];
|
|
377
|
+
}
|
|
378
|
+
const byMember = new Map();
|
|
379
|
+
for (const assignment of this.item().assignments ?? []) {
|
|
380
|
+
const quantity = Math.max(1, assignment.quantity ?? 1);
|
|
381
|
+
const prev = byMember.get(assignment.memberId) ?? { quantity: 0, hasPacked: false };
|
|
382
|
+
byMember.set(assignment.memberId, {
|
|
383
|
+
quantity: prev.quantity + quantity,
|
|
384
|
+
hasPacked: prev.hasPacked || assignment.status === 'packed' || assignment.status === 'confirmed'
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
return Array.from(byMember.entries())
|
|
388
|
+
.map(([memberId, value]) => {
|
|
389
|
+
const member = this.members().find(m => m.id === memberId);
|
|
390
|
+
return {
|
|
391
|
+
key: memberId,
|
|
392
|
+
text: `${member?.name ?? memberId}: ${value.quantity}x`,
|
|
393
|
+
tone: (value.hasPacked ? 'success' : 'warning')
|
|
394
|
+
};
|
|
395
|
+
})
|
|
396
|
+
.sort((a, b) => {
|
|
397
|
+
const aIsCurrent = a.key === this.currentUserId();
|
|
398
|
+
const bIsCurrent = b.key === this.currentUserId();
|
|
399
|
+
if (aIsCurrent && !bIsCurrent)
|
|
400
|
+
return -1;
|
|
401
|
+
if (!aIsCurrent && bIsCurrent)
|
|
402
|
+
return 1;
|
|
403
|
+
return a.text.localeCompare(b.text);
|
|
404
|
+
});
|
|
405
|
+
}, ...(ngDevMode ? [{ debugName: "sharedCountChips" }] : []));
|
|
406
|
+
memberAssignmentStatus(memberId) {
|
|
407
|
+
const assignments = this.item().assignments ?? [];
|
|
408
|
+
const a = assignments.find(a => a.memberId === memberId);
|
|
409
|
+
if (!a)
|
|
410
|
+
return 'none';
|
|
411
|
+
if (a.status === 'confirmed')
|
|
412
|
+
return 'confirmed';
|
|
413
|
+
if (a.status === 'packed')
|
|
414
|
+
return 'packed';
|
|
415
|
+
return 'assigned';
|
|
416
|
+
}
|
|
417
|
+
memberAssignedQuantity(memberId) {
|
|
418
|
+
const assignments = this.item().assignments ?? [];
|
|
419
|
+
const assignment = assignments.find(a => a.memberId === memberId);
|
|
420
|
+
if (!assignment) {
|
|
421
|
+
return 0;
|
|
422
|
+
}
|
|
423
|
+
return Math.max(1, assignment.quantity ?? 1);
|
|
424
|
+
}
|
|
425
|
+
memberRingTone(memberId) {
|
|
426
|
+
return 'custom';
|
|
427
|
+
}
|
|
428
|
+
memberRingColor(memberId) {
|
|
429
|
+
const status = this.memberAssignmentStatus(memberId);
|
|
430
|
+
if (status === 'confirmed' || status === 'packed')
|
|
431
|
+
return '#4caf50';
|
|
432
|
+
if (status === 'assigned')
|
|
433
|
+
return '#f1c84a';
|
|
434
|
+
return '#cad8d0';
|
|
435
|
+
}
|
|
436
|
+
memberBadge(memberId) {
|
|
437
|
+
const status = this.memberAssignmentStatus(memberId);
|
|
438
|
+
if (status === 'confirmed' || status === 'packed') {
|
|
439
|
+
return { icon: '✓', tone: 'success', ariaLabel: 'Bestätigt' };
|
|
440
|
+
}
|
|
441
|
+
if (status === 'assigned') {
|
|
442
|
+
return { icon: '?', tone: 'neutral', ariaLabel: 'Zugewiesen' };
|
|
443
|
+
}
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
memberBackground(member) {
|
|
447
|
+
switch (member.type) {
|
|
448
|
+
case 'child':
|
|
449
|
+
case 'baby':
|
|
450
|
+
return '#fdf2d9';
|
|
451
|
+
case 'pet':
|
|
452
|
+
return '#efe6fb';
|
|
453
|
+
default:
|
|
454
|
+
return '#e8f0ea';
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
isMemberChecked(memberId) {
|
|
458
|
+
return this.item().visibility === 'shared'
|
|
459
|
+
? this.isSharedAssigned(memberId)
|
|
460
|
+
: this.isAssignmentChecked(memberId);
|
|
461
|
+
}
|
|
462
|
+
hasAnyAssignment(memberId) {
|
|
463
|
+
const assignments = this.item().assignments ?? [];
|
|
464
|
+
return assignments.some(a => a.memberId === memberId);
|
|
465
|
+
}
|
|
466
|
+
canPackForMember(memberId) {
|
|
467
|
+
const permissions = this.permissions();
|
|
468
|
+
if (permissions.readOnly) {
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
return memberId === this.currentUserId() || permissions.isOrganizer;
|
|
472
|
+
}
|
|
473
|
+
isAssignmentChecked(memberId) {
|
|
474
|
+
const assignments = this.item().assignments ?? [];
|
|
475
|
+
return assignments.some(a => a.memberId === memberId && (a.status === 'packed' || a.status === 'confirmed'));
|
|
476
|
+
}
|
|
477
|
+
isSharedAssigned(memberId) {
|
|
478
|
+
const assignments = this.item().assignments ?? [];
|
|
479
|
+
return assignments.some(a => a.memberId === memberId);
|
|
480
|
+
}
|
|
481
|
+
canAssignShared(memberId) {
|
|
482
|
+
const permissions = this.permissions();
|
|
483
|
+
if (permissions.readOnly) {
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
if (permissions.isGuest && memberId !== this.currentUserId()) {
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
return true;
|
|
490
|
+
}
|
|
491
|
+
onSoloToggle(checked) {
|
|
492
|
+
this.itemChecked.emit({ itemId: this.item().id, checked });
|
|
493
|
+
}
|
|
494
|
+
onPrivateToggle(checked) {
|
|
495
|
+
this.itemChecked.emit({ itemId: this.item().id, checked });
|
|
496
|
+
}
|
|
497
|
+
onSharedToggle(memberId, quantity = 1) {
|
|
498
|
+
const assigned = !this.isSharedAssigned(memberId);
|
|
499
|
+
const safeQuantity = Math.max(1, quantity);
|
|
500
|
+
const label = assigned
|
|
501
|
+
? `Willst du dich mit ${safeQuantity}x fuer dieses Shared-Item eintragen?`
|
|
502
|
+
: 'Willst du deine Zuordnung fuer dieses Shared-Item entfernen?';
|
|
503
|
+
if (!globalThis.confirm(label)) {
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
this.itemAssigned.emit({ itemId: this.item().id, memberId, assigned, quantity: assigned ? safeQuantity : undefined });
|
|
507
|
+
}
|
|
508
|
+
setSharedAssignment(memberId, assigned, quantity) {
|
|
509
|
+
const safeQuantity = quantity !== undefined ? Math.max(1, quantity) : undefined;
|
|
510
|
+
this.itemAssigned.emit({
|
|
511
|
+
itemId: this.item().id,
|
|
512
|
+
memberId,
|
|
513
|
+
assigned,
|
|
514
|
+
quantity: assigned ? safeQuantity : undefined
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
setPersonalAssignment(memberId, assigned) {
|
|
518
|
+
this.itemAssigned.emit({
|
|
519
|
+
itemId: this.item().id,
|
|
520
|
+
memberId,
|
|
521
|
+
assigned
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
onPersonalToggle(memberId) {
|
|
525
|
+
const checked = !this.isAssignmentChecked(memberId);
|
|
526
|
+
this.personalItemToggled.emit({ itemId: this.item().id, memberId, checked });
|
|
527
|
+
}
|
|
528
|
+
deleteItem() {
|
|
529
|
+
if (!this.canEditOrDeleteItem()) {
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
this.itemDeleted.emit(this.item().id);
|
|
533
|
+
}
|
|
534
|
+
requestEdit() {
|
|
535
|
+
if (!this.canEditOrDeleteItem()) {
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
this.itemEditRequested.emit(this.item().id);
|
|
539
|
+
}
|
|
540
|
+
requestAssign() {
|
|
541
|
+
if (!this.canShowAssignCta()) {
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
if (this.item().visibility === 'personal') {
|
|
545
|
+
this.openPersonalAssignDialog();
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
this.openOverlay();
|
|
549
|
+
}
|
|
550
|
+
openPersonalAssignDialog() {
|
|
551
|
+
if (this.item().visibility !== 'personal') {
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
const candidates = this.personalAssignableMembers();
|
|
555
|
+
if (candidates.length === 0) {
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
this.closeCtaMenu();
|
|
559
|
+
this.closeOverlay();
|
|
560
|
+
this.closeDelegateDialog();
|
|
561
|
+
this.closeAvatarDialog();
|
|
562
|
+
this.quantityPickerOpen.set(false);
|
|
563
|
+
this.personalAssignSearch.set('');
|
|
564
|
+
this.personalAssignMemberId.set(candidates[0].id);
|
|
565
|
+
this.personalAssignDialogOpen.set(true);
|
|
566
|
+
}
|
|
567
|
+
closePersonalAssignDialog() {
|
|
568
|
+
this.personalAssignDialogOpen.set(false);
|
|
569
|
+
this.personalAssignSearch.set('');
|
|
570
|
+
this.personalAssignMemberId.set(null);
|
|
571
|
+
}
|
|
572
|
+
onPersonalAssignSearch(event) {
|
|
573
|
+
this.personalAssignSearch.set(event.target.value);
|
|
574
|
+
const filtered = this.filteredPersonalAssignableMembers();
|
|
575
|
+
const selectedMemberId = this.personalAssignMemberId();
|
|
576
|
+
if (!selectedMemberId || !filtered.some(member => member.id === selectedMemberId)) {
|
|
577
|
+
this.personalAssignMemberId.set(filtered[0]?.id ?? null);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
selectPersonalAssignMember(memberId) {
|
|
581
|
+
this.personalAssignMemberId.set(memberId);
|
|
582
|
+
}
|
|
583
|
+
confirmPersonalAssignment() {
|
|
584
|
+
const member = this.selectedPersonalAssignMember();
|
|
585
|
+
if (!member) {
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
if (!globalThis.confirm(`${member.name} diesem Personal-Item zuweisen?`)) {
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
this.setPersonalAssignment(member.id, true);
|
|
592
|
+
this.closePersonalAssignDialog();
|
|
593
|
+
}
|
|
594
|
+
onPersonalAvatarClick(memberId) {
|
|
595
|
+
if (this.item().visibility !== 'personal' || !this.canManagePersonalAssignments() || !this.hasAnyAssignment(memberId)) {
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
const member = this.members().find(entry => entry.id === memberId);
|
|
599
|
+
if (!globalThis.confirm(`${member?.name ?? memberId} von diesem Personal-Item entfernen?`)) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
this.setPersonalAssignment(memberId, false);
|
|
603
|
+
}
|
|
604
|
+
onAssignMeToggle() {
|
|
605
|
+
if (this.selfAssignedToShared()) {
|
|
606
|
+
this.openDelegateDialog();
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
if (this.requiredQuantity() <= 1) {
|
|
610
|
+
this.onSharedToggle(this.currentUserId(), 1);
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
const missing = this.sharedMissingSlots();
|
|
614
|
+
if (missing <= 0) {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
this.quantityPickerValue.set(Math.min(this.quantityPickerValue(), missing));
|
|
618
|
+
this.quantityPickerOpen.update(open => !open);
|
|
619
|
+
}
|
|
620
|
+
openDelegateDialog() {
|
|
621
|
+
if (this.item().visibility !== 'shared') {
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
const candidates = this.delegateCandidates();
|
|
625
|
+
if (candidates.length === 0) {
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
this.closeAvatarDialog();
|
|
629
|
+
this.quantityPickerOpen.set(false);
|
|
630
|
+
this.delegateMemberId.set(candidates[0].id);
|
|
631
|
+
this.delegateQuantity.set(1);
|
|
632
|
+
this.delegateDialogOpen.set(true);
|
|
633
|
+
}
|
|
634
|
+
closeDelegateDialog() {
|
|
635
|
+
this.delegateDialogOpen.set(false);
|
|
636
|
+
this.delegateMemberId.set(null);
|
|
637
|
+
this.delegateQuantity.set(1);
|
|
638
|
+
}
|
|
639
|
+
onDelegateMemberChange(event) {
|
|
640
|
+
this.delegateMemberId.set(event.target.value || null);
|
|
641
|
+
}
|
|
642
|
+
increaseDelegateQuantity() {
|
|
643
|
+
this.delegateQuantity.update(value => Math.min(this.requiredQuantity(), value + 1));
|
|
644
|
+
}
|
|
645
|
+
decreaseDelegateQuantity() {
|
|
646
|
+
this.delegateQuantity.update(value => Math.max(1, value - 1));
|
|
647
|
+
}
|
|
648
|
+
confirmDelegateAssignment() {
|
|
649
|
+
const memberId = this.delegateMemberId();
|
|
650
|
+
if (!memberId) {
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
const target = this.members().find(member => member.id === memberId);
|
|
654
|
+
const quantity = Math.max(1, this.delegateQuantity());
|
|
655
|
+
const confirmationText = `Auch User mit Account können von anderen übernommen werden.`;
|
|
656
|
+
if (!globalThis.confirm(`Zuordnung setzen: ${target?.name ?? memberId} (${quantity}x)?\n${confirmationText}`)) {
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
this.setSharedAssignment(memberId, true, quantity);
|
|
660
|
+
this.closeDelegateDialog();
|
|
661
|
+
}
|
|
662
|
+
onSharedAvatarClick(memberId) {
|
|
663
|
+
if (this.item().visibility !== 'shared') {
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
const quantity = this.memberAssignedQuantity(memberId);
|
|
667
|
+
if (quantity <= 0) {
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
this.closeDelegateDialog();
|
|
671
|
+
this.quantityPickerOpen.set(false);
|
|
672
|
+
this.avatarDialogMemberId.set(memberId);
|
|
673
|
+
this.avatarDialogQuantity.set(quantity);
|
|
674
|
+
this.avatarDialogOpen.set(true);
|
|
675
|
+
}
|
|
676
|
+
closeAvatarDialog() {
|
|
677
|
+
this.avatarDialogOpen.set(false);
|
|
678
|
+
this.avatarDialogMemberId.set(null);
|
|
679
|
+
this.avatarDialogQuantity.set(1);
|
|
680
|
+
}
|
|
681
|
+
increaseAvatarDialogQuantity() {
|
|
682
|
+
this.avatarDialogQuantity.update(value => Math.min(this.requiredQuantity(), value + 1));
|
|
683
|
+
}
|
|
684
|
+
decreaseAvatarDialogQuantity() {
|
|
685
|
+
this.avatarDialogQuantity.update(value => Math.max(0, value - 1));
|
|
686
|
+
}
|
|
687
|
+
confirmAvatarDialogUpdate() {
|
|
688
|
+
const memberId = this.avatarDialogMemberId();
|
|
689
|
+
if (!memberId) {
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
const quantity = this.avatarDialogQuantity();
|
|
693
|
+
const member = this.members().find(m => m.id === memberId);
|
|
694
|
+
if (quantity === 0) {
|
|
695
|
+
if (!globalThis.confirm(`Zuordnung fuer ${member?.name ?? memberId} entfernen?`)) {
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
this.setSharedAssignment(memberId, false);
|
|
699
|
+
this.closeAvatarDialog();
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
if (!globalThis.confirm(`Menge fuer ${member?.name ?? memberId} auf ${quantity} setzen?`)) {
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
this.setSharedAssignment(memberId, true, quantity);
|
|
706
|
+
this.closeAvatarDialog();
|
|
707
|
+
}
|
|
708
|
+
increaseSelfAssignQuantity() {
|
|
709
|
+
const max = this.selfAssignableMaxQuantity();
|
|
710
|
+
this.quantityPickerValue.update(value => Math.min(max, value + 1));
|
|
711
|
+
}
|
|
712
|
+
decreaseSelfAssignQuantity() {
|
|
713
|
+
this.quantityPickerValue.update(value => Math.max(1, value - 1));
|
|
714
|
+
}
|
|
715
|
+
cancelSelfAssignQuantity() {
|
|
716
|
+
this.quantityPickerOpen.set(false);
|
|
717
|
+
this.quantityPickerValue.set(1);
|
|
718
|
+
}
|
|
719
|
+
confirmSelfAssignQuantity() {
|
|
720
|
+
const quantity = Math.max(1, Math.min(this.quantityPickerValue(), this.selfAssignableMaxQuantity()));
|
|
721
|
+
this.quantityPickerOpen.set(false);
|
|
722
|
+
if (!globalThis.confirm(`Willst du dich mit ${quantity}x fuer dieses Shared-Item eintragen?`)) {
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
this.setSharedAssignment(this.currentUserId(), true, quantity);
|
|
726
|
+
}
|
|
727
|
+
toggleCtaMenu() {
|
|
728
|
+
this.ctaMenuOpen.update(open => !open);
|
|
729
|
+
}
|
|
730
|
+
closeCtaMenu() {
|
|
731
|
+
this.ctaMenuOpen.set(false);
|
|
732
|
+
}
|
|
733
|
+
onMenuAssign() {
|
|
734
|
+
this.closeCtaMenu();
|
|
735
|
+
this.requestAssign();
|
|
736
|
+
}
|
|
737
|
+
onMenuEdit() {
|
|
738
|
+
this.closeCtaMenu();
|
|
739
|
+
this.requestEdit();
|
|
740
|
+
}
|
|
741
|
+
onMenuDelete() {
|
|
742
|
+
this.closeCtaMenu();
|
|
743
|
+
this.deleteItem();
|
|
744
|
+
}
|
|
745
|
+
openOverlay() {
|
|
746
|
+
this.closeCtaMenu();
|
|
747
|
+
this.overlaySearch.set('');
|
|
748
|
+
this.quantityPickerOpen.set(false);
|
|
749
|
+
this.closeDelegateDialog();
|
|
750
|
+
this.closeAvatarDialog();
|
|
751
|
+
this.closePersonalAssignDialog();
|
|
752
|
+
this.overlayOpen.set(true);
|
|
753
|
+
this.memberOverlayRequested.emit({
|
|
754
|
+
itemId: this.item().id,
|
|
755
|
+
itemName: this.item().name,
|
|
756
|
+
members: this.overlayMembers()
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
closeOverlay() {
|
|
760
|
+
this.overlayOpen.set(false);
|
|
761
|
+
this.overlaySearch.set('');
|
|
762
|
+
}
|
|
763
|
+
onOverlaySearch(event) {
|
|
764
|
+
this.overlaySearch.set(event.target.value);
|
|
765
|
+
}
|
|
766
|
+
toggleFromOverlay(memberId) {
|
|
767
|
+
if (this.item().visibility === 'shared') {
|
|
768
|
+
this.onSharedToggle(memberId);
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
if (this.item().visibility === 'personal') {
|
|
772
|
+
this.onPersonalToggle(memberId);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
privateItemOwnerLabel() {
|
|
776
|
+
if (!this.item().privateOwnerVisible) {
|
|
777
|
+
return 'Privat';
|
|
778
|
+
}
|
|
779
|
+
const ownerId = this.item().createdBy ?? this.item().assignments?.[0]?.memberId;
|
|
780
|
+
const owner = this.members().find(member => member.id === ownerId);
|
|
781
|
+
return owner?.name ?? 'Privat';
|
|
782
|
+
}
|
|
783
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: PackingListItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
784
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: PackingListItemComponent, isStandalone: true, selector: "c2g-packing-list-item", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, members: { classPropertyName: "members", publicName: "members", isSignal: true, isRequired: true, transformFunction: null }, currentUserId: { classPropertyName: "currentUserId", publicName: "currentUserId", isSignal: true, isRequired: true, transformFunction: null }, permissions: { classPropertyName: "permissions", publicName: "permissions", isSignal: true, isRequired: true, transformFunction: null }, selectedMemberIds: { classPropertyName: "selectedMemberIds", publicName: "selectedMemberIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemChecked: "itemChecked", itemAssigned: "itemAssigned", itemDeleted: "itemDeleted", itemEditRequested: "itemEditRequested", personalItemToggled: "personalItemToggled", memberOverlayRequested: "memberOverlayRequested" }, ngImport: i0, template: "<div class=\"c2g-pl-item\" [class.c2g-pl-item--packed]=\"isPacked()\">\n\n <div class=\"c2g-pl-item__row\">\n\n <!-- Solo: echte Checkbox -->\n @if (permissions().isSolo) {\n <button\n class=\"c2g-pl-item__check\"\n [class.c2g-pl-item__check--done]=\"isPacked()\"\n type=\"button\"\n [disabled]=\"permissions().readOnly\"\n [attr.aria-pressed]=\"isPacked()\"\n [attr.aria-label]=\"isPacked() ? 'Als ungepackt markieren' : 'Als gepackt markieren'\"\n (click)=\"onSoloToggle(!isPacked())\">\n @if (isPacked()) {\n <span class=\"c2g-pl-item__check-mark\" aria-hidden=\"true\">\u2713</span>\n }\n </button>\n } @else {\n <!-- Gruppen-Item: farbiger Fortschritts-Dot -->\n <span\n class=\"c2g-pl-item__dot\"\n [class.c2g-pl-item__dot--danger]=\"progressTone() === 'danger'\"\n [class.c2g-pl-item__dot--warning]=\"progressTone() === 'warning'\"\n [class.c2g-pl-item__dot--success]=\"progressTone() === 'success'\"\n [attr.title]=\"progressRatioText() + ' bereit'\"\n aria-hidden=\"true\">\n </span>\n }\n\n <!-- Body -->\n <div class=\"c2g-pl-item__body\">\n\n <!-- Zeile 1: Name + Tags + Mengen-Badge -->\n <div class=\"c2g-pl-item__title-row\">\n <span class=\"c2g-pl-item__name\">{{ item().name }}</span>\n <div class=\"c2g-pl-item__tags\">\n @if (item().essential) {\n <span class=\"c2g-pl-item__tag c2g-pl-item__tag--essential\">Pflicht</span>\n }\n @if (item().weather) {\n <span class=\"c2g-pl-item__tag c2g-pl-item__tag--weather\">Wetter</span>\n }\n @if (item().visibility === 'private') {\n <span class=\"c2g-pl-item__tag c2g-pl-item__tag--private\">\uD83D\uDD12 {{ privateItemOwnerLabel() }}</span>\n }\n </div>\n\n @if (quantityBadge(); as badge) {\n <c2g-badge\n class=\"c2g-pl-item__qty-badge\"\n [value]=\"badge.value\"\n [tone]=\"badge.tone\"\n variant=\"subtle\"\n size=\"sm\"\n [attr.aria-label]=\"'Gepackt: ' + badge.value\">\n </c2g-badge>\n }\n </div>\n\n <!-- Zeile 2: Mitglieder / Avatare -->\n @if (!permissions().isSolo) {\n <div class=\"c2g-pl-item__members-row\">\n\n @if (item().visibility === 'shared') {\n <!-- Assigned-Avatare -->\n @for (member of checkedPreview(); track member.id) {\n <button\n class=\"c2g-pl-item__avatar-btn\"\n type=\"button\"\n [disabled]=\"permissions().readOnly\"\n [attr.aria-label]=\"member.name + ' \u2013 antippen zum Bearbeiten'\"\n (click)=\"onSharedAvatarClick(member.id)\">\n <c2g-avatar\n size=\"sm\"\n [name]=\"member.name\"\n [initials]=\"member.initials || ''\"\n [backgroundColor]=\"memberBackground(member)\"\n [ringTone]=\"memberRingTone(member.id)\"\n [ringColor]=\"memberRingColor(member.id)\"\n [badge]=\"memberBadge(member.id)\">\n </c2g-avatar>\n </button>\n }\n\n @if (hiddenCheckedCount() > 0) {\n <div class=\"c2g-pl-item__more-wrap\">\n <button class=\"c2g-pl-item__more\" type=\"button\" (click)=\"openOverlay()\">\n +{{ hiddenCheckedCount() }}\n </button>\n @if (overlayOpen()) {\n <div class=\"c2g-pl-item__overlay\" role=\"dialog\" aria-label=\"Alle Mitglieder\">\n <div class=\"c2g-pl-item__overlay-head\">\n <strong>{{ item().name }}</strong>\n <button type=\"button\" class=\"c2g-pl-item__overlay-close\" (click)=\"closeOverlay()\">\u2715</button>\n </div>\n <input class=\"c2g-pl-item__overlay-search\" type=\"search\" [value]=\"overlaySearch()\" placeholder=\"Mitglied suchen...\" (input)=\"onOverlaySearch($event)\" />\n <div class=\"c2g-pl-item__overlay-list\">\n @for (member of filteredOverlayMembers(); track member.memberId) {\n <button class=\"c2g-pl-item__overlay-row\" type=\"button\" [disabled]=\"!member.canToggle\" (click)=\"toggleFromOverlay(member.memberId)\">\n <span>{{ member.memberName }}</span>\n <span class=\"c2g-pl-item__overlay-status\" [class.c2g-pl-item__overlay-status--done]=\"member.checked\">{{ member.checked ? '\u2713 bereit' : 'ausstehend' }}</span>\n </button>\n }\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Empty state -->\n @if (checkedMembers().length === 0) {\n <span class=\"c2g-pl-item__empty\">Noch niemand eingetragen</span>\n }\n\n <!-- + Eintragen Button -->\n @if (canSelfAssignShared()) {\n <button\n class=\"c2g-pl-item__join-btn\"\n [class.c2g-pl-item__join-btn--active]=\"selfAssignedToShared()\"\n type=\"button\"\n (click)=\"onAssignMeToggle()\">\n {{ joinBtnLabel() }}\n </button>\n }\n\n } @else if (item().visibility === 'personal') {\n <!-- Personal: wer hat es f\u00FCr sich -->\n @for (member of personalAssignedPreview(); track member.id) {\n <button\n class=\"c2g-pl-item__avatar-btn\"\n type=\"button\"\n [disabled]=\"!canManagePersonalAssignments()\"\n [attr.aria-label]=\"member.name + ' \u2013 antippen zum Entfernen'\"\n (click)=\"onPersonalAvatarClick(member.id)\">\n <c2g-avatar\n size=\"sm\"\n [name]=\"member.name\"\n [initials]=\"member.initials || ''\"\n [backgroundColor]=\"memberBackground(member)\"\n [ringTone]=\"memberRingTone(member.id)\"\n [ringColor]=\"memberRingColor(member.id)\"\n [badge]=\"memberBadge(member.id)\">\n </c2g-avatar>\n </button>\n }\n\n @if (personalHiddenAssignedCount() > 0) {\n <div class=\"c2g-pl-item__more-wrap\">\n <button class=\"c2g-pl-item__more\" type=\"button\" (click)=\"openOverlay()\">\n +{{ personalHiddenAssignedCount() }}\n </button>\n @if (overlayOpen()) {\n <div class=\"c2g-pl-item__overlay\" role=\"dialog\" aria-label=\"Alle Mitglieder\">\n <div class=\"c2g-pl-item__overlay-head\">\n <strong>{{ item().name }}</strong>\n <button type=\"button\" class=\"c2g-pl-item__overlay-close\" (click)=\"closeOverlay()\">\u2715</button>\n </div>\n <input class=\"c2g-pl-item__overlay-search\" type=\"search\" [value]=\"overlaySearch()\" placeholder=\"Mitglied suchen...\" (input)=\"onOverlaySearch($event)\" />\n <div class=\"c2g-pl-item__overlay-list\">\n @for (member of filteredOverlayMembers(); track member.memberId) {\n <button class=\"c2g-pl-item__overlay-row\" type=\"button\" [disabled]=\"!member.canToggle\" (click)=\"toggleFromOverlay(member.memberId)\">\n <span>{{ member.memberName }}</span>\n <span class=\"c2g-pl-item__overlay-status\" [class.c2g-pl-item__overlay-status--done]=\"member.checked\">{{ member.checked ? '\u2713 bereit' : 'ausstehend' }}</span>\n </button>\n }\n </div>\n </div>\n }\n </div>\n }\n\n @if (personalAssignedMembers().length === 0) {\n <span class=\"c2g-pl-item__empty\">Nicht zugewiesen</span>\n }\n\n @if (canManagePersonalAssignments()) {\n <button class=\"c2g-pl-item__join-btn\" type=\"button\" (click)=\"openPersonalAssignDialog()\">\n + Zuweisen\n </button>\n }\n }\n\n </div>\n }\n\n </div>\n\n <!-- Aktions-Spalte: \u22EF Men\u00FC -->\n @if (canShowAssignCta() || canEditOrDeleteItem()) {\n <div class=\"c2g-pl-item__cta\">\n <button\n class=\"c2g-pl-item__menu-trigger\"\n type=\"button\"\n aria-label=\"Aktionen\"\n [attr.aria-expanded]=\"ctaMenuOpen()\"\n (click)=\"toggleCtaMenu()\">\n \u22EF\n </button>\n\n @if (ctaMenuOpen()) {\n <div class=\"c2g-pl-item__menu\" role=\"menu\">\n @if (canShowAssignCta()) {\n <button class=\"c2g-pl-item__menu-item\" type=\"button\" role=\"menuitem\" (click)=\"onMenuAssign()\">\n <span class=\"c2g-pl-item__menu-icon\">\uD83D\uDC64</span> {{ isPlanner() ? 'Zuweisen' : 'Einpacken' }}\n </button>\n }\n @if (canEditOrDeleteItem()) {\n <button class=\"c2g-pl-item__menu-item\" type=\"button\" role=\"menuitem\" (click)=\"onMenuEdit()\">\n <span class=\"c2g-pl-item__menu-icon\">\u270F\uFE0F</span> Bearbeiten\n </button>\n <div class=\"c2g-pl-item__menu-divider\"></div>\n <button class=\"c2g-pl-item__menu-item c2g-pl-item__menu-item--danger\" type=\"button\" role=\"menuitem\" (click)=\"onMenuDelete()\">\n <span class=\"c2g-pl-item__menu-icon\">\uD83D\uDDD1</span> L\u00F6schen\n </button>\n }\n </div>\n }\n </div>\n }\n\n </div>\n\n <!-- Quantity Picker (Shared: eigene Menge) -->\n @if (quantityPickerOpen()) {\n <div class=\"c2g-pl-item__picker\" role=\"group\" aria-label=\"Menge w\u00E4hlen\">\n <span class=\"c2g-pl-item__picker-label\">{{ pickerLabel() }}</span>\n <button class=\"c2g-pl-item__qty-btn\" type=\"button\" (click)=\"decreaseSelfAssignQuantity()\">\u2212</button>\n <span class=\"c2g-pl-item__qty-val\">{{ quantityPickerValue() }}x</span>\n <button class=\"c2g-pl-item__qty-btn\" type=\"button\" (click)=\"increaseSelfAssignQuantity()\">+</button>\n <button class=\"c2g-pl-item__qty-ok\" type=\"button\" (click)=\"confirmSelfAssignQuantity()\">Best\u00E4tigen</button>\n <button class=\"c2g-pl-item__qty-cancel\" type=\"button\" (click)=\"cancelSelfAssignQuantity()\">Abbrechen</button>\n </div>\n }\n\n <!-- Delegate Dialog (Organizer weist anderem zu) -->\n @if (delegateDialogOpen()) {\n <div class=\"c2g-pl-item__picker\" role=\"dialog\" aria-label=\"Mitglied zuweisen\">\n <span class=\"c2g-pl-item__picker-label\">{{ delegatePickerLabel() }}</span>\n <select class=\"c2g-pl-item__select\" [value]=\"delegateMemberId() ?? ''\" (change)=\"onDelegateMemberChange($event)\">\n @for (member of delegateCandidates(); track member.id) {\n <option [value]=\"member.id\">{{ member.name }}</option>\n }\n </select>\n <button class=\"c2g-pl-item__qty-btn\" type=\"button\" (click)=\"decreaseDelegateQuantity()\">\u2212</button>\n <span class=\"c2g-pl-item__qty-val\">{{ delegateQuantity() }}x</span>\n <button class=\"c2g-pl-item__qty-btn\" type=\"button\" (click)=\"increaseDelegateQuantity()\">+</button>\n <button class=\"c2g-pl-item__qty-ok\" type=\"button\" (click)=\"confirmDelegateAssignment()\">Best\u00E4tigen</button>\n <button class=\"c2g-pl-item__qty-cancel\" type=\"button\" (click)=\"closeDelegateDialog()\">Abbrechen</button>\n </div>\n }\n\n <!-- Avatar Dialog (Menge eines Zugewiesenen \u00E4ndern) -->\n @if (avatarDialogOpen()) {\n <div class=\"c2g-pl-item__picker\" role=\"dialog\" aria-label=\"Zuteilung bearbeiten\">\n <span class=\"c2g-pl-item__picker-label\">Menge anpassen \u2013 0 zum Entfernen</span>\n <button class=\"c2g-pl-item__qty-btn\" type=\"button\" (click)=\"decreaseAvatarDialogQuantity()\">\u2212</button>\n <span class=\"c2g-pl-item__qty-val\">{{ avatarDialogQuantity() }}x</span>\n <button class=\"c2g-pl-item__qty-btn\" type=\"button\" (click)=\"increaseAvatarDialogQuantity()\">+</button>\n <button class=\"c2g-pl-item__qty-ok\" type=\"button\" (click)=\"confirmAvatarDialogUpdate()\">Best\u00E4tigen</button>\n <button class=\"c2g-pl-item__qty-cancel\" type=\"button\" (click)=\"closeAvatarDialog()\">Abbrechen</button>\n </div>\n }\n\n <!-- Personal Assign Panel -->\n @if (personalAssignDialogOpen()) {\n <div class=\"c2g-pl-item__assign-panel\" role=\"dialog\" aria-label=\"Mitglied zuweisen\">\n <input\n class=\"c2g-pl-item__assign-search\"\n type=\"search\"\n [value]=\"personalAssignSearch()\"\n placeholder=\"Mitglied suchen...\"\n (input)=\"onPersonalAssignSearch($event)\" />\n <div class=\"c2g-pl-item__assign-list\">\n @for (member of filteredPersonalAssignableMembers(); track member.id) {\n <button\n class=\"c2g-pl-item__assign-option\"\n [class.c2g-pl-item__assign-option--active]=\"personalAssignMemberId() === member.id\"\n type=\"button\"\n (click)=\"selectPersonalAssignMember(member.id)\">\n <c2g-avatar\n size=\"sm\"\n [name]=\"member.name\"\n [initials]=\"member.initials || ''\"\n [backgroundColor]=\"memberBackground(member)\">\n </c2g-avatar>\n <span>{{ member.name }}</span>\n </button>\n }\n </div>\n <div class=\"c2g-pl-item__assign-footer\">\n <button class=\"c2g-pl-item__qty-ok\" type=\"button\" [disabled]=\"!selectedPersonalAssignMember()\" (click)=\"confirmPersonalAssignment()\">Zuweisen</button>\n <button class=\"c2g-pl-item__qty-cancel\" type=\"button\" (click)=\"closePersonalAssignDialog()\">Abbrechen</button>\n </div>\n </div>\n }\n\n <!-- Hinweis -->\n @if (item().hint) {\n <p class=\"c2g-pl-item__hint\">{{ item().hint }}</p>\n }\n\n <!-- Shared: Mengen-Chips -->\n @if (sharedCountChips().length > 0) {\n <div class=\"c2g-pl-item__chip-row\">\n @for (chip of sharedCountChips(); track chip.key) {\n <c2g-badge [value]=\"chip.text\" [tone]=\"chip.tone\" variant=\"subtle\" size=\"sm\"></c2g-badge>\n }\n </div>\n }\n\n <!-- Personal: Status-Chips -->\n @if (personalStatusBadges().length > 0) {\n <div class=\"c2g-pl-item__chip-row\">\n @for (badge of personalStatusBadges(); track badge.key) {\n <c2g-badge\n [value]=\"badge.text\"\n [tone]=\"badge.tone\"\n variant=\"subtle\"\n size=\"sm\"\n [dot]=\"true\"\n [dotColor]=\"badge.dotColor\">\n </c2g-badge>\n }\n </div>\n }\n\n\n</div>\n", styles: [".c2g-pl-item{border:1px solid var(--c2g-color-outline-variant);border-radius:.625rem;padding:.65rem .75rem;background:var(--c2g-color-surface);display:grid;gap:.45rem;position:relative;transition:background .15s ease,border-color .15s ease}.c2g-pl-item--packed{background:color-mix(in srgb,#16a34a 6%,var(--c2g-color-surface));border-color:color-mix(in srgb,#16a34a 25%,transparent)}.c2g-pl-item--packed .c2g-pl-item__name{color:var(--c2g-color-text-muted);text-decoration:line-through;text-decoration-color:color-mix(in srgb,#16a34a 50%,transparent)}.c2g-pl-item__row{display:grid;grid-template-columns:.875rem minmax(0,1fr) auto;gap:.6rem;align-items:start}.c2g-pl-item__check{width:1.1rem;height:1.1rem;border:2px solid var(--c2g-color-outline);border-radius:.25rem;background:var(--c2g-color-surface);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;padding:0;margin-top:.15rem;flex-shrink:0;transition:background .12s,border-color .12s}.c2g-pl-item__check:hover:not(:disabled){border-color:var(--c2g-color-secondary-dark)}.c2g-pl-item__check--done{background:var(--c2g-color-secondary-dark, #2d6a4f);border-color:var(--c2g-color-secondary-dark, #2d6a4f)}.c2g-pl-item__check-mark{color:#fff;font-size:.65rem;font-weight:800;line-height:1}.c2g-pl-item__dot{width:.5rem;height:.5rem;border-radius:50%;margin-top:.35rem;flex-shrink:0;background:var(--c2g-color-outline)}.c2g-pl-item__dot--danger{background:#ef4444}.c2g-pl-item__dot--warning{background:#f59e0b}.c2g-pl-item__dot--success{background:#16a34a}.c2g-pl-item__body{display:grid;gap:.4rem;min-width:0}.c2g-pl-item__title-row{display:flex;align-items:center;gap:.4rem;flex-wrap:wrap;min-width:0}.c2g-pl-item__qty-badge{margin-left:auto;flex-shrink:0}.c2g-pl-item__name{font-weight:600;font-size:.875rem;color:var(--c2g-color-text-primary);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:color .15s}.c2g-pl-item__tags{display:inline-flex;align-items:center;gap:.25rem;flex-wrap:wrap;flex-shrink:0}.c2g-pl-item__tag{font-size:.65rem;font-weight:500;border-radius:99px;padding:.1rem .4rem;background:var(--c2g-color-neutral-100);color:var(--c2g-color-text-muted);white-space:nowrap}.c2g-pl-item__tag--essential{background:color-mix(in srgb,#f59e0b 15%,transparent);color:#92400e}.c2g-pl-item__tag--weather{background:color-mix(in srgb,#3b82f6 12%,transparent);color:#1d4ed8}.c2g-pl-item__tag--private{background:var(--c2g-color-primary-container);color:var(--c2g-color-on-primary-container)}.c2g-pl-item__members-row{display:flex;align-items:center;flex-wrap:wrap;gap:.35rem}.c2g-pl-item__avatar-btn{border:0;background:transparent;padding:0;cursor:pointer;border-radius:50%;display:inline-flex}.c2g-pl-item__avatar-btn:disabled{cursor:default;opacity:.85}.c2g-pl-item__avatar-btn:not(:disabled):hover{transform:translateY(-1px);transition:transform .1s}.c2g-pl-item__more-wrap{position:relative;display:inline-flex}.c2g-pl-item__more{height:1.75rem;min-width:1.75rem;padding:0 .4rem;border-radius:99px;border:1px dashed var(--c2g-color-outline);background:transparent;color:var(--c2g-color-text-muted);font-size:.72rem;font-weight:600;cursor:pointer}.c2g-pl-item__more:hover{background:var(--c2g-color-neutral-100)}.c2g-pl-item__empty{font-size:.75rem;color:var(--c2g-color-text-muted);font-style:italic}.c2g-pl-item__join-btn{display:inline-flex;align-items:center;height:1.75rem;padding:0 .6rem;border-radius:99px;border:1.5px dashed var(--c2g-color-secondary-dark);background:transparent;color:var(--c2g-color-secondary-dark);font-size:.72rem;font-weight:600;cursor:pointer;white-space:nowrap;transition:background .1s}.c2g-pl-item__join-btn:hover{background:var(--c2g-color-secondary-container)}.c2g-pl-item__join-btn--active{border-style:solid;background:var(--c2g-color-secondary-container)}.c2g-pl-item__cta{position:relative;display:inline-grid;justify-items:end}.c2g-pl-item__menu-trigger{width:1.75rem;height:1.75rem;border:1px solid var(--c2g-color-outline-variant);border-radius:.4rem;background:transparent;color:var(--c2g-color-text-muted);font-size:1rem;line-height:1;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;padding:0}.c2g-pl-item__menu-trigger:hover{background:var(--c2g-color-neutral-100);color:var(--c2g-color-text-primary)}.c2g-pl-item__menu{position:absolute;top:calc(100% + .3rem);right:0;min-width:10rem;border:1px solid var(--c2g-color-outline-variant);border-radius:.65rem;background:var(--c2g-color-surface);box-shadow:0 8px 24px #152b2124;padding:.3rem;display:grid;gap:.1rem;z-index:20}.c2g-pl-item__menu-item{display:flex;align-items:center;gap:.45rem;border:0;background:transparent;color:var(--c2g-color-text-primary);border-radius:.45rem;min-height:2rem;padding:.25rem .55rem;font-size:.82rem;text-align:left;cursor:pointer;width:100%}.c2g-pl-item__menu-item:hover{background:var(--c2g-color-neutral-100)}.c2g-pl-item__menu-item--danger{color:var(--c2g-color-error)}.c2g-pl-item__menu-icon{font-size:.85rem;line-height:1;flex-shrink:0}.c2g-pl-item__menu-divider{height:1px;background:var(--c2g-color-outline-variant);margin:.15rem .3rem}.c2g-pl-item__picker{display:flex;align-items:center;flex-wrap:wrap;gap:.3rem;padding:.45rem .6rem;border:1px solid var(--c2g-color-outline-variant);border-radius:.5rem;background:var(--c2g-color-neutral-50)}.c2g-pl-item__picker-label{font-size:.75rem;color:var(--c2g-color-text-muted);width:100%}.c2g-pl-item__qty-btn{width:1.6rem;height:1.6rem;border:1px solid var(--c2g-color-outline);border-radius:.35rem;background:var(--c2g-color-surface);color:var(--c2g-color-text-primary);font-size:1.1rem;line-height:1;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;padding:0}.c2g-pl-item__qty-val{min-width:2rem;text-align:center;font-weight:700;font-size:.85rem}.c2g-pl-item__select{height:1.7rem;border:1px solid var(--c2g-color-outline);border-radius:.35rem;background:var(--c2g-color-surface);color:var(--c2g-color-text-primary);font-size:.78rem;padding:0 .5rem}.c2g-pl-item__qty-ok{height:1.7rem;padding:0 .65rem;border-radius:.35rem;border:1px solid var(--c2g-color-secondary-dark);background:var(--c2g-color-secondary-container);color:var(--c2g-color-secondary-dark);font-size:.76rem;font-weight:600;cursor:pointer}.c2g-pl-item__qty-ok:disabled{opacity:.45;cursor:not-allowed}.c2g-pl-item__qty-cancel{height:1.7rem;padding:0 .5rem;border-radius:.35rem;border:1px solid transparent;background:transparent;color:var(--c2g-color-text-muted);font-size:.76rem;cursor:pointer}.c2g-pl-item__qty-cancel:hover{background:var(--c2g-color-neutral-100)}.c2g-pl-item__assign-panel{display:grid;gap:.35rem;padding:.55rem;border:1px solid var(--c2g-color-outline-variant);border-radius:.65rem;background:var(--c2g-color-neutral-50)}.c2g-pl-item__assign-search{height:2rem;border:1px solid var(--c2g-color-outline);border-radius:.4rem;background:var(--c2g-color-surface);color:var(--c2g-color-text-primary);font-size:.82rem;padding:0 .65rem}.c2g-pl-item__assign-list{display:grid;gap:.2rem;max-height:10rem;overflow:auto}.c2g-pl-item__assign-option{display:flex;align-items:center;gap:.55rem;border:1px solid var(--c2g-color-outline-variant);background:var(--c2g-color-surface);color:var(--c2g-color-text-primary);border-radius:.45rem;min-height:2.2rem;padding:.3rem .6rem;font-size:.82rem;cursor:pointer;text-align:left}.c2g-pl-item__assign-option:hover{background:var(--c2g-color-neutral-100)}.c2g-pl-item__assign-option--active{border-color:var(--c2g-color-secondary-dark);background:var(--c2g-color-secondary-container)}.c2g-pl-item__assign-footer{display:flex;gap:.35rem}.c2g-pl-item__hint{margin:0;font-size:.78rem;color:var(--c2g-color-text-muted)}.c2g-pl-item__chip-row{display:flex;flex-wrap:wrap;gap:.3rem}.c2g-pl-item__overlay{position:absolute;top:calc(100% + .35rem);left:0;width:min(22rem,92vw);border:1px solid var(--c2g-color-outline);background:var(--c2g-color-surface);border-radius:.7rem;box-shadow:0 12px 32px #152b212e;padding:.6rem;display:grid;gap:.4rem;z-index:30}.c2g-pl-item__overlay-head{display:flex;align-items:center;justify-content:space-between;font-size:.88rem;font-weight:600}.c2g-pl-item__overlay-close{border:0;background:transparent;cursor:pointer;color:var(--c2g-color-text-secondary);font-size:.9rem;padding:.1rem .25rem;border-radius:.3rem}.c2g-pl-item__overlay-close:hover{background:var(--c2g-color-neutral-100)}.c2g-pl-item__overlay-search{height:2rem;border:1px solid var(--c2g-color-outline);border-radius:.4rem;padding:0 .6rem;font-size:.82rem}.c2g-pl-item__overlay-list{max-height:12rem;overflow:auto;display:grid;gap:.2rem}.c2g-pl-item__overlay-row{border:1px solid var(--c2g-color-outline-variant);background:var(--c2g-color-surface);border-radius:.45rem;padding:.4rem .55rem;display:flex;justify-content:space-between;align-items:center;font-size:.82rem;cursor:pointer;text-align:left}.c2g-pl-item__overlay-row:hover:not(:disabled){background:var(--c2g-color-neutral-100)}.c2g-pl-item__overlay-row:disabled{cursor:not-allowed;opacity:.55}.c2g-pl-item__overlay-status{font-size:.7rem;border-radius:99px;padding:.1rem .45rem;background:var(--c2g-color-neutral-100);color:var(--c2g-color-text-muted);white-space:nowrap}.c2g-pl-item__overlay-status--done{background:color-mix(in srgb,#16a34a 12%,transparent);color:#15803d}@media(max-width:480px){.c2g-pl-item__overlay{left:0;right:auto;width:calc(100vw - 2rem)}}\n"], dependencies: [{ kind: "component", type: AvatarComponent, selector: "c2g-avatar", inputs: ["name", "initials", "imageUrl", "ariaLabel", "size", "backgroundColor", "textColor", "ringTone", "ringColor", "clickable", "disabled", "badge"], outputs: ["avatarClick"] }, { kind: "component", type: BadgeComponent, selector: "c2g-badge", inputs: ["value", "tone", "variant", "size", "max", "dot", "dotOnly", "dotRing", "dotPulse", "dotColor", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
785
|
+
}
|
|
786
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: PackingListItemComponent, decorators: [{
|
|
787
|
+
type: Component,
|
|
788
|
+
args: [{ selector: 'c2g-packing-list-item', standalone: true, imports: [AvatarComponent, BadgeComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"c2g-pl-item\" [class.c2g-pl-item--packed]=\"isPacked()\">\n\n <div class=\"c2g-pl-item__row\">\n\n <!-- Solo: echte Checkbox -->\n @if (permissions().isSolo) {\n <button\n class=\"c2g-pl-item__check\"\n [class.c2g-pl-item__check--done]=\"isPacked()\"\n type=\"button\"\n [disabled]=\"permissions().readOnly\"\n [attr.aria-pressed]=\"isPacked()\"\n [attr.aria-label]=\"isPacked() ? 'Als ungepackt markieren' : 'Als gepackt markieren'\"\n (click)=\"onSoloToggle(!isPacked())\">\n @if (isPacked()) {\n <span class=\"c2g-pl-item__check-mark\" aria-hidden=\"true\">\u2713</span>\n }\n </button>\n } @else {\n <!-- Gruppen-Item: farbiger Fortschritts-Dot -->\n <span\n class=\"c2g-pl-item__dot\"\n [class.c2g-pl-item__dot--danger]=\"progressTone() === 'danger'\"\n [class.c2g-pl-item__dot--warning]=\"progressTone() === 'warning'\"\n [class.c2g-pl-item__dot--success]=\"progressTone() === 'success'\"\n [attr.title]=\"progressRatioText() + ' bereit'\"\n aria-hidden=\"true\">\n </span>\n }\n\n <!-- Body -->\n <div class=\"c2g-pl-item__body\">\n\n <!-- Zeile 1: Name + Tags + Mengen-Badge -->\n <div class=\"c2g-pl-item__title-row\">\n <span class=\"c2g-pl-item__name\">{{ item().name }}</span>\n <div class=\"c2g-pl-item__tags\">\n @if (item().essential) {\n <span class=\"c2g-pl-item__tag c2g-pl-item__tag--essential\">Pflicht</span>\n }\n @if (item().weather) {\n <span class=\"c2g-pl-item__tag c2g-pl-item__tag--weather\">Wetter</span>\n }\n @if (item().visibility === 'private') {\n <span class=\"c2g-pl-item__tag c2g-pl-item__tag--private\">\uD83D\uDD12 {{ privateItemOwnerLabel() }}</span>\n }\n </div>\n\n @if (quantityBadge(); as badge) {\n <c2g-badge\n class=\"c2g-pl-item__qty-badge\"\n [value]=\"badge.value\"\n [tone]=\"badge.tone\"\n variant=\"subtle\"\n size=\"sm\"\n [attr.aria-label]=\"'Gepackt: ' + badge.value\">\n </c2g-badge>\n }\n </div>\n\n <!-- Zeile 2: Mitglieder / Avatare -->\n @if (!permissions().isSolo) {\n <div class=\"c2g-pl-item__members-row\">\n\n @if (item().visibility === 'shared') {\n <!-- Assigned-Avatare -->\n @for (member of checkedPreview(); track member.id) {\n <button\n class=\"c2g-pl-item__avatar-btn\"\n type=\"button\"\n [disabled]=\"permissions().readOnly\"\n [attr.aria-label]=\"member.name + ' \u2013 antippen zum Bearbeiten'\"\n (click)=\"onSharedAvatarClick(member.id)\">\n <c2g-avatar\n size=\"sm\"\n [name]=\"member.name\"\n [initials]=\"member.initials || ''\"\n [backgroundColor]=\"memberBackground(member)\"\n [ringTone]=\"memberRingTone(member.id)\"\n [ringColor]=\"memberRingColor(member.id)\"\n [badge]=\"memberBadge(member.id)\">\n </c2g-avatar>\n </button>\n }\n\n @if (hiddenCheckedCount() > 0) {\n <div class=\"c2g-pl-item__more-wrap\">\n <button class=\"c2g-pl-item__more\" type=\"button\" (click)=\"openOverlay()\">\n +{{ hiddenCheckedCount() }}\n </button>\n @if (overlayOpen()) {\n <div class=\"c2g-pl-item__overlay\" role=\"dialog\" aria-label=\"Alle Mitglieder\">\n <div class=\"c2g-pl-item__overlay-head\">\n <strong>{{ item().name }}</strong>\n <button type=\"button\" class=\"c2g-pl-item__overlay-close\" (click)=\"closeOverlay()\">\u2715</button>\n </div>\n <input class=\"c2g-pl-item__overlay-search\" type=\"search\" [value]=\"overlaySearch()\" placeholder=\"Mitglied suchen...\" (input)=\"onOverlaySearch($event)\" />\n <div class=\"c2g-pl-item__overlay-list\">\n @for (member of filteredOverlayMembers(); track member.memberId) {\n <button class=\"c2g-pl-item__overlay-row\" type=\"button\" [disabled]=\"!member.canToggle\" (click)=\"toggleFromOverlay(member.memberId)\">\n <span>{{ member.memberName }}</span>\n <span class=\"c2g-pl-item__overlay-status\" [class.c2g-pl-item__overlay-status--done]=\"member.checked\">{{ member.checked ? '\u2713 bereit' : 'ausstehend' }}</span>\n </button>\n }\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Empty state -->\n @if (checkedMembers().length === 0) {\n <span class=\"c2g-pl-item__empty\">Noch niemand eingetragen</span>\n }\n\n <!-- + Eintragen Button -->\n @if (canSelfAssignShared()) {\n <button\n class=\"c2g-pl-item__join-btn\"\n [class.c2g-pl-item__join-btn--active]=\"selfAssignedToShared()\"\n type=\"button\"\n (click)=\"onAssignMeToggle()\">\n {{ joinBtnLabel() }}\n </button>\n }\n\n } @else if (item().visibility === 'personal') {\n <!-- Personal: wer hat es f\u00FCr sich -->\n @for (member of personalAssignedPreview(); track member.id) {\n <button\n class=\"c2g-pl-item__avatar-btn\"\n type=\"button\"\n [disabled]=\"!canManagePersonalAssignments()\"\n [attr.aria-label]=\"member.name + ' \u2013 antippen zum Entfernen'\"\n (click)=\"onPersonalAvatarClick(member.id)\">\n <c2g-avatar\n size=\"sm\"\n [name]=\"member.name\"\n [initials]=\"member.initials || ''\"\n [backgroundColor]=\"memberBackground(member)\"\n [ringTone]=\"memberRingTone(member.id)\"\n [ringColor]=\"memberRingColor(member.id)\"\n [badge]=\"memberBadge(member.id)\">\n </c2g-avatar>\n </button>\n }\n\n @if (personalHiddenAssignedCount() > 0) {\n <div class=\"c2g-pl-item__more-wrap\">\n <button class=\"c2g-pl-item__more\" type=\"button\" (click)=\"openOverlay()\">\n +{{ personalHiddenAssignedCount() }}\n </button>\n @if (overlayOpen()) {\n <div class=\"c2g-pl-item__overlay\" role=\"dialog\" aria-label=\"Alle Mitglieder\">\n <div class=\"c2g-pl-item__overlay-head\">\n <strong>{{ item().name }}</strong>\n <button type=\"button\" class=\"c2g-pl-item__overlay-close\" (click)=\"closeOverlay()\">\u2715</button>\n </div>\n <input class=\"c2g-pl-item__overlay-search\" type=\"search\" [value]=\"overlaySearch()\" placeholder=\"Mitglied suchen...\" (input)=\"onOverlaySearch($event)\" />\n <div class=\"c2g-pl-item__overlay-list\">\n @for (member of filteredOverlayMembers(); track member.memberId) {\n <button class=\"c2g-pl-item__overlay-row\" type=\"button\" [disabled]=\"!member.canToggle\" (click)=\"toggleFromOverlay(member.memberId)\">\n <span>{{ member.memberName }}</span>\n <span class=\"c2g-pl-item__overlay-status\" [class.c2g-pl-item__overlay-status--done]=\"member.checked\">{{ member.checked ? '\u2713 bereit' : 'ausstehend' }}</span>\n </button>\n }\n </div>\n </div>\n }\n </div>\n }\n\n @if (personalAssignedMembers().length === 0) {\n <span class=\"c2g-pl-item__empty\">Nicht zugewiesen</span>\n }\n\n @if (canManagePersonalAssignments()) {\n <button class=\"c2g-pl-item__join-btn\" type=\"button\" (click)=\"openPersonalAssignDialog()\">\n + Zuweisen\n </button>\n }\n }\n\n </div>\n }\n\n </div>\n\n <!-- Aktions-Spalte: \u22EF Men\u00FC -->\n @if (canShowAssignCta() || canEditOrDeleteItem()) {\n <div class=\"c2g-pl-item__cta\">\n <button\n class=\"c2g-pl-item__menu-trigger\"\n type=\"button\"\n aria-label=\"Aktionen\"\n [attr.aria-expanded]=\"ctaMenuOpen()\"\n (click)=\"toggleCtaMenu()\">\n \u22EF\n </button>\n\n @if (ctaMenuOpen()) {\n <div class=\"c2g-pl-item__menu\" role=\"menu\">\n @if (canShowAssignCta()) {\n <button class=\"c2g-pl-item__menu-item\" type=\"button\" role=\"menuitem\" (click)=\"onMenuAssign()\">\n <span class=\"c2g-pl-item__menu-icon\">\uD83D\uDC64</span> {{ isPlanner() ? 'Zuweisen' : 'Einpacken' }}\n </button>\n }\n @if (canEditOrDeleteItem()) {\n <button class=\"c2g-pl-item__menu-item\" type=\"button\" role=\"menuitem\" (click)=\"onMenuEdit()\">\n <span class=\"c2g-pl-item__menu-icon\">\u270F\uFE0F</span> Bearbeiten\n </button>\n <div class=\"c2g-pl-item__menu-divider\"></div>\n <button class=\"c2g-pl-item__menu-item c2g-pl-item__menu-item--danger\" type=\"button\" role=\"menuitem\" (click)=\"onMenuDelete()\">\n <span class=\"c2g-pl-item__menu-icon\">\uD83D\uDDD1</span> L\u00F6schen\n </button>\n }\n </div>\n }\n </div>\n }\n\n </div>\n\n <!-- Quantity Picker (Shared: eigene Menge) -->\n @if (quantityPickerOpen()) {\n <div class=\"c2g-pl-item__picker\" role=\"group\" aria-label=\"Menge w\u00E4hlen\">\n <span class=\"c2g-pl-item__picker-label\">{{ pickerLabel() }}</span>\n <button class=\"c2g-pl-item__qty-btn\" type=\"button\" (click)=\"decreaseSelfAssignQuantity()\">\u2212</button>\n <span class=\"c2g-pl-item__qty-val\">{{ quantityPickerValue() }}x</span>\n <button class=\"c2g-pl-item__qty-btn\" type=\"button\" (click)=\"increaseSelfAssignQuantity()\">+</button>\n <button class=\"c2g-pl-item__qty-ok\" type=\"button\" (click)=\"confirmSelfAssignQuantity()\">Best\u00E4tigen</button>\n <button class=\"c2g-pl-item__qty-cancel\" type=\"button\" (click)=\"cancelSelfAssignQuantity()\">Abbrechen</button>\n </div>\n }\n\n <!-- Delegate Dialog (Organizer weist anderem zu) -->\n @if (delegateDialogOpen()) {\n <div class=\"c2g-pl-item__picker\" role=\"dialog\" aria-label=\"Mitglied zuweisen\">\n <span class=\"c2g-pl-item__picker-label\">{{ delegatePickerLabel() }}</span>\n <select class=\"c2g-pl-item__select\" [value]=\"delegateMemberId() ?? ''\" (change)=\"onDelegateMemberChange($event)\">\n @for (member of delegateCandidates(); track member.id) {\n <option [value]=\"member.id\">{{ member.name }}</option>\n }\n </select>\n <button class=\"c2g-pl-item__qty-btn\" type=\"button\" (click)=\"decreaseDelegateQuantity()\">\u2212</button>\n <span class=\"c2g-pl-item__qty-val\">{{ delegateQuantity() }}x</span>\n <button class=\"c2g-pl-item__qty-btn\" type=\"button\" (click)=\"increaseDelegateQuantity()\">+</button>\n <button class=\"c2g-pl-item__qty-ok\" type=\"button\" (click)=\"confirmDelegateAssignment()\">Best\u00E4tigen</button>\n <button class=\"c2g-pl-item__qty-cancel\" type=\"button\" (click)=\"closeDelegateDialog()\">Abbrechen</button>\n </div>\n }\n\n <!-- Avatar Dialog (Menge eines Zugewiesenen \u00E4ndern) -->\n @if (avatarDialogOpen()) {\n <div class=\"c2g-pl-item__picker\" role=\"dialog\" aria-label=\"Zuteilung bearbeiten\">\n <span class=\"c2g-pl-item__picker-label\">Menge anpassen \u2013 0 zum Entfernen</span>\n <button class=\"c2g-pl-item__qty-btn\" type=\"button\" (click)=\"decreaseAvatarDialogQuantity()\">\u2212</button>\n <span class=\"c2g-pl-item__qty-val\">{{ avatarDialogQuantity() }}x</span>\n <button class=\"c2g-pl-item__qty-btn\" type=\"button\" (click)=\"increaseAvatarDialogQuantity()\">+</button>\n <button class=\"c2g-pl-item__qty-ok\" type=\"button\" (click)=\"confirmAvatarDialogUpdate()\">Best\u00E4tigen</button>\n <button class=\"c2g-pl-item__qty-cancel\" type=\"button\" (click)=\"closeAvatarDialog()\">Abbrechen</button>\n </div>\n }\n\n <!-- Personal Assign Panel -->\n @if (personalAssignDialogOpen()) {\n <div class=\"c2g-pl-item__assign-panel\" role=\"dialog\" aria-label=\"Mitglied zuweisen\">\n <input\n class=\"c2g-pl-item__assign-search\"\n type=\"search\"\n [value]=\"personalAssignSearch()\"\n placeholder=\"Mitglied suchen...\"\n (input)=\"onPersonalAssignSearch($event)\" />\n <div class=\"c2g-pl-item__assign-list\">\n @for (member of filteredPersonalAssignableMembers(); track member.id) {\n <button\n class=\"c2g-pl-item__assign-option\"\n [class.c2g-pl-item__assign-option--active]=\"personalAssignMemberId() === member.id\"\n type=\"button\"\n (click)=\"selectPersonalAssignMember(member.id)\">\n <c2g-avatar\n size=\"sm\"\n [name]=\"member.name\"\n [initials]=\"member.initials || ''\"\n [backgroundColor]=\"memberBackground(member)\">\n </c2g-avatar>\n <span>{{ member.name }}</span>\n </button>\n }\n </div>\n <div class=\"c2g-pl-item__assign-footer\">\n <button class=\"c2g-pl-item__qty-ok\" type=\"button\" [disabled]=\"!selectedPersonalAssignMember()\" (click)=\"confirmPersonalAssignment()\">Zuweisen</button>\n <button class=\"c2g-pl-item__qty-cancel\" type=\"button\" (click)=\"closePersonalAssignDialog()\">Abbrechen</button>\n </div>\n </div>\n }\n\n <!-- Hinweis -->\n @if (item().hint) {\n <p class=\"c2g-pl-item__hint\">{{ item().hint }}</p>\n }\n\n <!-- Shared: Mengen-Chips -->\n @if (sharedCountChips().length > 0) {\n <div class=\"c2g-pl-item__chip-row\">\n @for (chip of sharedCountChips(); track chip.key) {\n <c2g-badge [value]=\"chip.text\" [tone]=\"chip.tone\" variant=\"subtle\" size=\"sm\"></c2g-badge>\n }\n </div>\n }\n\n <!-- Personal: Status-Chips -->\n @if (personalStatusBadges().length > 0) {\n <div class=\"c2g-pl-item__chip-row\">\n @for (badge of personalStatusBadges(); track badge.key) {\n <c2g-badge\n [value]=\"badge.text\"\n [tone]=\"badge.tone\"\n variant=\"subtle\"\n size=\"sm\"\n [dot]=\"true\"\n [dotColor]=\"badge.dotColor\">\n </c2g-badge>\n }\n </div>\n }\n\n\n</div>\n", styles: [".c2g-pl-item{border:1px solid var(--c2g-color-outline-variant);border-radius:.625rem;padding:.65rem .75rem;background:var(--c2g-color-surface);display:grid;gap:.45rem;position:relative;transition:background .15s ease,border-color .15s ease}.c2g-pl-item--packed{background:color-mix(in srgb,#16a34a 6%,var(--c2g-color-surface));border-color:color-mix(in srgb,#16a34a 25%,transparent)}.c2g-pl-item--packed .c2g-pl-item__name{color:var(--c2g-color-text-muted);text-decoration:line-through;text-decoration-color:color-mix(in srgb,#16a34a 50%,transparent)}.c2g-pl-item__row{display:grid;grid-template-columns:.875rem minmax(0,1fr) auto;gap:.6rem;align-items:start}.c2g-pl-item__check{width:1.1rem;height:1.1rem;border:2px solid var(--c2g-color-outline);border-radius:.25rem;background:var(--c2g-color-surface);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;padding:0;margin-top:.15rem;flex-shrink:0;transition:background .12s,border-color .12s}.c2g-pl-item__check:hover:not(:disabled){border-color:var(--c2g-color-secondary-dark)}.c2g-pl-item__check--done{background:var(--c2g-color-secondary-dark, #2d6a4f);border-color:var(--c2g-color-secondary-dark, #2d6a4f)}.c2g-pl-item__check-mark{color:#fff;font-size:.65rem;font-weight:800;line-height:1}.c2g-pl-item__dot{width:.5rem;height:.5rem;border-radius:50%;margin-top:.35rem;flex-shrink:0;background:var(--c2g-color-outline)}.c2g-pl-item__dot--danger{background:#ef4444}.c2g-pl-item__dot--warning{background:#f59e0b}.c2g-pl-item__dot--success{background:#16a34a}.c2g-pl-item__body{display:grid;gap:.4rem;min-width:0}.c2g-pl-item__title-row{display:flex;align-items:center;gap:.4rem;flex-wrap:wrap;min-width:0}.c2g-pl-item__qty-badge{margin-left:auto;flex-shrink:0}.c2g-pl-item__name{font-weight:600;font-size:.875rem;color:var(--c2g-color-text-primary);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:color .15s}.c2g-pl-item__tags{display:inline-flex;align-items:center;gap:.25rem;flex-wrap:wrap;flex-shrink:0}.c2g-pl-item__tag{font-size:.65rem;font-weight:500;border-radius:99px;padding:.1rem .4rem;background:var(--c2g-color-neutral-100);color:var(--c2g-color-text-muted);white-space:nowrap}.c2g-pl-item__tag--essential{background:color-mix(in srgb,#f59e0b 15%,transparent);color:#92400e}.c2g-pl-item__tag--weather{background:color-mix(in srgb,#3b82f6 12%,transparent);color:#1d4ed8}.c2g-pl-item__tag--private{background:var(--c2g-color-primary-container);color:var(--c2g-color-on-primary-container)}.c2g-pl-item__members-row{display:flex;align-items:center;flex-wrap:wrap;gap:.35rem}.c2g-pl-item__avatar-btn{border:0;background:transparent;padding:0;cursor:pointer;border-radius:50%;display:inline-flex}.c2g-pl-item__avatar-btn:disabled{cursor:default;opacity:.85}.c2g-pl-item__avatar-btn:not(:disabled):hover{transform:translateY(-1px);transition:transform .1s}.c2g-pl-item__more-wrap{position:relative;display:inline-flex}.c2g-pl-item__more{height:1.75rem;min-width:1.75rem;padding:0 .4rem;border-radius:99px;border:1px dashed var(--c2g-color-outline);background:transparent;color:var(--c2g-color-text-muted);font-size:.72rem;font-weight:600;cursor:pointer}.c2g-pl-item__more:hover{background:var(--c2g-color-neutral-100)}.c2g-pl-item__empty{font-size:.75rem;color:var(--c2g-color-text-muted);font-style:italic}.c2g-pl-item__join-btn{display:inline-flex;align-items:center;height:1.75rem;padding:0 .6rem;border-radius:99px;border:1.5px dashed var(--c2g-color-secondary-dark);background:transparent;color:var(--c2g-color-secondary-dark);font-size:.72rem;font-weight:600;cursor:pointer;white-space:nowrap;transition:background .1s}.c2g-pl-item__join-btn:hover{background:var(--c2g-color-secondary-container)}.c2g-pl-item__join-btn--active{border-style:solid;background:var(--c2g-color-secondary-container)}.c2g-pl-item__cta{position:relative;display:inline-grid;justify-items:end}.c2g-pl-item__menu-trigger{width:1.75rem;height:1.75rem;border:1px solid var(--c2g-color-outline-variant);border-radius:.4rem;background:transparent;color:var(--c2g-color-text-muted);font-size:1rem;line-height:1;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;padding:0}.c2g-pl-item__menu-trigger:hover{background:var(--c2g-color-neutral-100);color:var(--c2g-color-text-primary)}.c2g-pl-item__menu{position:absolute;top:calc(100% + .3rem);right:0;min-width:10rem;border:1px solid var(--c2g-color-outline-variant);border-radius:.65rem;background:var(--c2g-color-surface);box-shadow:0 8px 24px #152b2124;padding:.3rem;display:grid;gap:.1rem;z-index:20}.c2g-pl-item__menu-item{display:flex;align-items:center;gap:.45rem;border:0;background:transparent;color:var(--c2g-color-text-primary);border-radius:.45rem;min-height:2rem;padding:.25rem .55rem;font-size:.82rem;text-align:left;cursor:pointer;width:100%}.c2g-pl-item__menu-item:hover{background:var(--c2g-color-neutral-100)}.c2g-pl-item__menu-item--danger{color:var(--c2g-color-error)}.c2g-pl-item__menu-icon{font-size:.85rem;line-height:1;flex-shrink:0}.c2g-pl-item__menu-divider{height:1px;background:var(--c2g-color-outline-variant);margin:.15rem .3rem}.c2g-pl-item__picker{display:flex;align-items:center;flex-wrap:wrap;gap:.3rem;padding:.45rem .6rem;border:1px solid var(--c2g-color-outline-variant);border-radius:.5rem;background:var(--c2g-color-neutral-50)}.c2g-pl-item__picker-label{font-size:.75rem;color:var(--c2g-color-text-muted);width:100%}.c2g-pl-item__qty-btn{width:1.6rem;height:1.6rem;border:1px solid var(--c2g-color-outline);border-radius:.35rem;background:var(--c2g-color-surface);color:var(--c2g-color-text-primary);font-size:1.1rem;line-height:1;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;padding:0}.c2g-pl-item__qty-val{min-width:2rem;text-align:center;font-weight:700;font-size:.85rem}.c2g-pl-item__select{height:1.7rem;border:1px solid var(--c2g-color-outline);border-radius:.35rem;background:var(--c2g-color-surface);color:var(--c2g-color-text-primary);font-size:.78rem;padding:0 .5rem}.c2g-pl-item__qty-ok{height:1.7rem;padding:0 .65rem;border-radius:.35rem;border:1px solid var(--c2g-color-secondary-dark);background:var(--c2g-color-secondary-container);color:var(--c2g-color-secondary-dark);font-size:.76rem;font-weight:600;cursor:pointer}.c2g-pl-item__qty-ok:disabled{opacity:.45;cursor:not-allowed}.c2g-pl-item__qty-cancel{height:1.7rem;padding:0 .5rem;border-radius:.35rem;border:1px solid transparent;background:transparent;color:var(--c2g-color-text-muted);font-size:.76rem;cursor:pointer}.c2g-pl-item__qty-cancel:hover{background:var(--c2g-color-neutral-100)}.c2g-pl-item__assign-panel{display:grid;gap:.35rem;padding:.55rem;border:1px solid var(--c2g-color-outline-variant);border-radius:.65rem;background:var(--c2g-color-neutral-50)}.c2g-pl-item__assign-search{height:2rem;border:1px solid var(--c2g-color-outline);border-radius:.4rem;background:var(--c2g-color-surface);color:var(--c2g-color-text-primary);font-size:.82rem;padding:0 .65rem}.c2g-pl-item__assign-list{display:grid;gap:.2rem;max-height:10rem;overflow:auto}.c2g-pl-item__assign-option{display:flex;align-items:center;gap:.55rem;border:1px solid var(--c2g-color-outline-variant);background:var(--c2g-color-surface);color:var(--c2g-color-text-primary);border-radius:.45rem;min-height:2.2rem;padding:.3rem .6rem;font-size:.82rem;cursor:pointer;text-align:left}.c2g-pl-item__assign-option:hover{background:var(--c2g-color-neutral-100)}.c2g-pl-item__assign-option--active{border-color:var(--c2g-color-secondary-dark);background:var(--c2g-color-secondary-container)}.c2g-pl-item__assign-footer{display:flex;gap:.35rem}.c2g-pl-item__hint{margin:0;font-size:.78rem;color:var(--c2g-color-text-muted)}.c2g-pl-item__chip-row{display:flex;flex-wrap:wrap;gap:.3rem}.c2g-pl-item__overlay{position:absolute;top:calc(100% + .35rem);left:0;width:min(22rem,92vw);border:1px solid var(--c2g-color-outline);background:var(--c2g-color-surface);border-radius:.7rem;box-shadow:0 12px 32px #152b212e;padding:.6rem;display:grid;gap:.4rem;z-index:30}.c2g-pl-item__overlay-head{display:flex;align-items:center;justify-content:space-between;font-size:.88rem;font-weight:600}.c2g-pl-item__overlay-close{border:0;background:transparent;cursor:pointer;color:var(--c2g-color-text-secondary);font-size:.9rem;padding:.1rem .25rem;border-radius:.3rem}.c2g-pl-item__overlay-close:hover{background:var(--c2g-color-neutral-100)}.c2g-pl-item__overlay-search{height:2rem;border:1px solid var(--c2g-color-outline);border-radius:.4rem;padding:0 .6rem;font-size:.82rem}.c2g-pl-item__overlay-list{max-height:12rem;overflow:auto;display:grid;gap:.2rem}.c2g-pl-item__overlay-row{border:1px solid var(--c2g-color-outline-variant);background:var(--c2g-color-surface);border-radius:.45rem;padding:.4rem .55rem;display:flex;justify-content:space-between;align-items:center;font-size:.82rem;cursor:pointer;text-align:left}.c2g-pl-item__overlay-row:hover:not(:disabled){background:var(--c2g-color-neutral-100)}.c2g-pl-item__overlay-row:disabled{cursor:not-allowed;opacity:.55}.c2g-pl-item__overlay-status{font-size:.7rem;border-radius:99px;padding:.1rem .45rem;background:var(--c2g-color-neutral-100);color:var(--c2g-color-text-muted);white-space:nowrap}.c2g-pl-item__overlay-status--done{background:color-mix(in srgb,#16a34a 12%,transparent);color:#15803d}@media(max-width:480px){.c2g-pl-item__overlay{left:0;right:auto;width:calc(100vw - 2rem)}}\n"] }]
|
|
789
|
+
}], propDecorators: { item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: true }] }], members: [{ type: i0.Input, args: [{ isSignal: true, alias: "members", required: true }] }], currentUserId: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentUserId", required: true }] }], permissions: [{ type: i0.Input, args: [{ isSignal: true, alias: "permissions", required: true }] }], selectedMemberIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedMemberIds", required: false }] }], itemChecked: [{ type: i0.Output, args: ["itemChecked"] }], itemAssigned: [{ type: i0.Output, args: ["itemAssigned"] }], itemDeleted: [{ type: i0.Output, args: ["itemDeleted"] }], itemEditRequested: [{ type: i0.Output, args: ["itemEditRequested"] }], personalItemToggled: [{ type: i0.Output, args: ["personalItemToggled"] }], memberOverlayRequested: [{ type: i0.Output, args: ["memberOverlayRequested"] }] } });
|
|
790
|
+
|
|
791
|
+
class PackingListCategoryComponent {
|
|
792
|
+
categoryKey = input.required(...(ngDevMode ? [{ debugName: "categoryKey" }] : []));
|
|
793
|
+
items = input.required(...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
794
|
+
members = input.required(...(ngDevMode ? [{ debugName: "members" }] : []));
|
|
795
|
+
selectedMemberIds = input([], ...(ngDevMode ? [{ debugName: "selectedMemberIds" }] : []));
|
|
796
|
+
currentUserId = input.required(...(ngDevMode ? [{ debugName: "currentUserId" }] : []));
|
|
797
|
+
permissions = input.required(...(ngDevMode ? [{ debugName: "permissions" }] : []));
|
|
798
|
+
expanded = input(true, ...(ngDevMode ? [{ debugName: "expanded" }] : []));
|
|
799
|
+
toggle = output();
|
|
800
|
+
itemChecked = output();
|
|
801
|
+
itemAssigned = output();
|
|
802
|
+
itemDeleted = output();
|
|
803
|
+
itemEditRequested = output();
|
|
804
|
+
personalItemToggled = output();
|
|
805
|
+
memberOverlayRequested = output();
|
|
806
|
+
category = computed(() => resolvePackingCategory(this.categoryKey()), ...(ngDevMode ? [{ debugName: "category" }] : []));
|
|
807
|
+
stats = computed(() => {
|
|
808
|
+
const list = this.items();
|
|
809
|
+
return {
|
|
810
|
+
total: list.length,
|
|
811
|
+
essential: list.filter(item => !!item.essential).length,
|
|
812
|
+
packed: list.filter(item => (item.assignments ?? []).some(a => a.status === 'packed' || a.status === 'confirmed')).length
|
|
813
|
+
};
|
|
814
|
+
}, ...(ngDevMode ? [{ debugName: "stats" }] : []));
|
|
815
|
+
onToggle() {
|
|
816
|
+
this.toggle.emit(this.categoryKey());
|
|
817
|
+
}
|
|
818
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: PackingListCategoryComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
819
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: PackingListCategoryComponent, isStandalone: true, selector: "c2g-packing-list-category", inputs: { categoryKey: { classPropertyName: "categoryKey", publicName: "categoryKey", isSignal: true, isRequired: true, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, members: { classPropertyName: "members", publicName: "members", isSignal: true, isRequired: true, transformFunction: null }, selectedMemberIds: { classPropertyName: "selectedMemberIds", publicName: "selectedMemberIds", isSignal: true, isRequired: false, transformFunction: null }, currentUserId: { classPropertyName: "currentUserId", publicName: "currentUserId", isSignal: true, isRequired: true, transformFunction: null }, permissions: { classPropertyName: "permissions", publicName: "permissions", isSignal: true, isRequired: true, transformFunction: null }, expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { toggle: "toggle", itemChecked: "itemChecked", itemAssigned: "itemAssigned", itemDeleted: "itemDeleted", itemEditRequested: "itemEditRequested", personalItemToggled: "personalItemToggled", memberOverlayRequested: "memberOverlayRequested" }, ngImport: i0, template: "<section class=\"c2g-pl-category\">\n <button type=\"button\" class=\"c2g-pl-category__header\" [class.c2g-pl-category__header--done]=\"stats().packed === stats().total && stats().total > 0\" (click)=\"onToggle()\">\n <mat-icon class=\"c2g-pl-category__icon\" aria-hidden=\"true\">{{ category().icon }}</mat-icon>\n <span class=\"c2g-pl-category__label\">{{ category().label ?? category().labelKey }}</span>\n <span class=\"c2g-pl-category__meta\">{{ stats().packed }}/{{ stats().total }}</span>\n <span class=\"c2g-pl-category__chevron\" [class.c2g-pl-category__chevron--open]=\"expanded()\" aria-hidden=\"true\">\u25BE</span>\n </button>\n\n @if (expanded()) {\n <div class=\"c2g-pl-category__items\">\n @for (item of items(); track item.id) {\n <c2g-packing-list-item\n [item]=\"item\"\n [members]=\"members()\"\n [selectedMemberIds]=\"selectedMemberIds()\"\n [currentUserId]=\"currentUserId()\"\n [permissions]=\"permissions()\"\n (itemChecked)=\"itemChecked.emit($event)\"\n (itemAssigned)=\"itemAssigned.emit($event)\"\n (itemDeleted)=\"itemDeleted.emit($event)\"\n (itemEditRequested)=\"itemEditRequested.emit($event)\"\n (personalItemToggled)=\"personalItemToggled.emit($event)\"\n (memberOverlayRequested)=\"memberOverlayRequested.emit($event)\">\n </c2g-packing-list-item>\n }\n </div>\n }\n</section>\n", styles: [".c2g-pl-category{border:1px solid var(--c2g-color-outline-variant);border-radius:.75rem;overflow:hidden;background:var(--c2g-color-surface)}.c2g-pl-category__header{width:100%;border:0;background:var(--c2g-color-neutral-50);padding:.6rem .75rem;display:grid;grid-template-columns:auto 1fr auto auto;gap:.45rem;align-items:center;text-align:left;cursor:pointer;font-weight:600;color:var(--c2g-color-text-primary)}.c2g-pl-category__header--done{background:var(--c2g-color-secondary-container);color:var(--c2g-color-secondary-dark)}.c2g-pl-category__label{font-size:.88rem}.c2g-pl-category__icon{font-size:1.1rem!important;width:1.1rem!important;height:1.1rem!important;line-height:1!important;color:var(--c2g-color-text-muted);flex-shrink:0}.c2g-pl-category__meta{font-size:.78rem;color:var(--c2g-color-text-muted);font-weight:500}.c2g-pl-category__chevron{font-size:.8rem;color:var(--c2g-color-text-muted);transition:transform .2s ease;line-height:1}.c2g-pl-category__chevron--open{transform:rotate(180deg)}.c2g-pl-category__items{display:grid;gap:.55rem;padding:.6rem}\n"], dependencies: [{ kind: "component", type: PackingListItemComponent, selector: "c2g-packing-list-item", inputs: ["item", "members", "currentUserId", "permissions", "selectedMemberIds"], outputs: ["itemChecked", "itemAssigned", "itemDeleted", "itemEditRequested", "personalItemToggled", "memberOverlayRequested"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
820
|
+
}
|
|
821
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: PackingListCategoryComponent, decorators: [{
|
|
822
|
+
type: Component,
|
|
823
|
+
args: [{ selector: 'c2g-packing-list-category', standalone: true, imports: [PackingListItemComponent, MatIconModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"c2g-pl-category\">\n <button type=\"button\" class=\"c2g-pl-category__header\" [class.c2g-pl-category__header--done]=\"stats().packed === stats().total && stats().total > 0\" (click)=\"onToggle()\">\n <mat-icon class=\"c2g-pl-category__icon\" aria-hidden=\"true\">{{ category().icon }}</mat-icon>\n <span class=\"c2g-pl-category__label\">{{ category().label ?? category().labelKey }}</span>\n <span class=\"c2g-pl-category__meta\">{{ stats().packed }}/{{ stats().total }}</span>\n <span class=\"c2g-pl-category__chevron\" [class.c2g-pl-category__chevron--open]=\"expanded()\" aria-hidden=\"true\">\u25BE</span>\n </button>\n\n @if (expanded()) {\n <div class=\"c2g-pl-category__items\">\n @for (item of items(); track item.id) {\n <c2g-packing-list-item\n [item]=\"item\"\n [members]=\"members()\"\n [selectedMemberIds]=\"selectedMemberIds()\"\n [currentUserId]=\"currentUserId()\"\n [permissions]=\"permissions()\"\n (itemChecked)=\"itemChecked.emit($event)\"\n (itemAssigned)=\"itemAssigned.emit($event)\"\n (itemDeleted)=\"itemDeleted.emit($event)\"\n (itemEditRequested)=\"itemEditRequested.emit($event)\"\n (personalItemToggled)=\"personalItemToggled.emit($event)\"\n (memberOverlayRequested)=\"memberOverlayRequested.emit($event)\">\n </c2g-packing-list-item>\n }\n </div>\n }\n</section>\n", styles: [".c2g-pl-category{border:1px solid var(--c2g-color-outline-variant);border-radius:.75rem;overflow:hidden;background:var(--c2g-color-surface)}.c2g-pl-category__header{width:100%;border:0;background:var(--c2g-color-neutral-50);padding:.6rem .75rem;display:grid;grid-template-columns:auto 1fr auto auto;gap:.45rem;align-items:center;text-align:left;cursor:pointer;font-weight:600;color:var(--c2g-color-text-primary)}.c2g-pl-category__header--done{background:var(--c2g-color-secondary-container);color:var(--c2g-color-secondary-dark)}.c2g-pl-category__label{font-size:.88rem}.c2g-pl-category__icon{font-size:1.1rem!important;width:1.1rem!important;height:1.1rem!important;line-height:1!important;color:var(--c2g-color-text-muted);flex-shrink:0}.c2g-pl-category__meta{font-size:.78rem;color:var(--c2g-color-text-muted);font-weight:500}.c2g-pl-category__chevron{font-size:.8rem;color:var(--c2g-color-text-muted);transition:transform .2s ease;line-height:1}.c2g-pl-category__chevron--open{transform:rotate(180deg)}.c2g-pl-category__items{display:grid;gap:.55rem;padding:.6rem}\n"] }]
|
|
824
|
+
}], propDecorators: { categoryKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "categoryKey", required: true }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], members: [{ type: i0.Input, args: [{ isSignal: true, alias: "members", required: true }] }], selectedMemberIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedMemberIds", required: false }] }], currentUserId: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentUserId", required: true }] }], permissions: [{ type: i0.Input, args: [{ isSignal: true, alias: "permissions", required: true }] }], expanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "expanded", required: false }] }], toggle: [{ type: i0.Output, args: ["toggle"] }], itemChecked: [{ type: i0.Output, args: ["itemChecked"] }], itemAssigned: [{ type: i0.Output, args: ["itemAssigned"] }], itemDeleted: [{ type: i0.Output, args: ["itemDeleted"] }], itemEditRequested: [{ type: i0.Output, args: ["itemEditRequested"] }], personalItemToggled: [{ type: i0.Output, args: ["personalItemToggled"] }], memberOverlayRequested: [{ type: i0.Output, args: ["memberOverlayRequested"] }] } });
|
|
825
|
+
|
|
826
|
+
class PackingListFiltersComponent {
|
|
827
|
+
filter = input.required(...(ngDevMode ? [{ debugName: "filter" }] : []));
|
|
828
|
+
members = input([], ...(ngDevMode ? [{ debugName: "members" }] : []));
|
|
829
|
+
labels = input(DEFAULT_PACKING_LIST_LABEL_KEYS, ...(ngDevMode ? [{ debugName: "labels" }] : []));
|
|
830
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
831
|
+
filterChange = output();
|
|
832
|
+
overlayRequested = output();
|
|
833
|
+
membersPanelOpen = signal(false, ...(ngDevMode ? [{ debugName: "membersPanelOpen" }] : []));
|
|
834
|
+
memberQuery = signal('', ...(ngDevMode ? [{ debugName: "memberQuery" }] : []));
|
|
835
|
+
memberSelectionLabel = computed(() => {
|
|
836
|
+
const selected = this.filter().memberIds.length;
|
|
837
|
+
if (selected === 0) {
|
|
838
|
+
return 'Alle User';
|
|
839
|
+
}
|
|
840
|
+
if (selected === 1) {
|
|
841
|
+
return '1 User';
|
|
842
|
+
}
|
|
843
|
+
return `${selected} User`;
|
|
844
|
+
}, ...(ngDevMode ? [{ debugName: "memberSelectionLabel" }] : []));
|
|
845
|
+
filteredMembers = computed(() => {
|
|
846
|
+
const query = this.memberQuery().trim().toLowerCase();
|
|
847
|
+
if (!query) {
|
|
848
|
+
return this.members();
|
|
849
|
+
}
|
|
850
|
+
return this.members().filter(member => {
|
|
851
|
+
const searchable = `${member.name} ${member.initials ?? ''}`.toLowerCase();
|
|
852
|
+
return searchable.includes(query);
|
|
853
|
+
});
|
|
854
|
+
}, ...(ngDevMode ? [{ debugName: "filteredMembers" }] : []));
|
|
855
|
+
onQueryChange(event) {
|
|
856
|
+
const query = event.target.value;
|
|
857
|
+
this.filterChange.emit({ ...this.filter(), query });
|
|
858
|
+
}
|
|
859
|
+
onVisibilityChange(event) {
|
|
860
|
+
const visibility = event.target.value;
|
|
861
|
+
this.filterChange.emit({ ...this.filter(), visibility });
|
|
862
|
+
}
|
|
863
|
+
onEssentialsOnlyToggle(event) {
|
|
864
|
+
const essentialsOnly = event.target.checked;
|
|
865
|
+
this.filterChange.emit({ ...this.filter(), essentialsOnly });
|
|
866
|
+
}
|
|
867
|
+
toggleMembersPanel() {
|
|
868
|
+
if (this.disabled()) {
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
this.membersPanelOpen.update(open => !open);
|
|
872
|
+
}
|
|
873
|
+
onMemberSearch(event) {
|
|
874
|
+
this.memberQuery.set(event.target.value);
|
|
875
|
+
}
|
|
876
|
+
isMemberSelected(memberId) {
|
|
877
|
+
return this.filter().memberIds.includes(memberId);
|
|
878
|
+
}
|
|
879
|
+
toggleMember(memberId) {
|
|
880
|
+
if (this.disabled()) {
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
const selected = new Set(this.filter().memberIds);
|
|
884
|
+
if (selected.has(memberId)) {
|
|
885
|
+
selected.delete(memberId);
|
|
886
|
+
}
|
|
887
|
+
else {
|
|
888
|
+
selected.add(memberId);
|
|
889
|
+
}
|
|
890
|
+
this.filterChange.emit({ ...this.filter(), memberIds: Array.from(selected) });
|
|
891
|
+
}
|
|
892
|
+
clearMemberSelection() {
|
|
893
|
+
this.filterChange.emit({ ...this.filter(), memberIds: [] });
|
|
894
|
+
}
|
|
895
|
+
selectAllMembers() {
|
|
896
|
+
this.filterChange.emit({ ...this.filter(), memberIds: this.members().map(member => member.id) });
|
|
897
|
+
}
|
|
898
|
+
requestExternalOverlay() {
|
|
899
|
+
this.overlayRequested.emit();
|
|
900
|
+
}
|
|
901
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: PackingListFiltersComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
902
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: PackingListFiltersComponent, isStandalone: true, selector: "c2g-packing-list-filters", inputs: { filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: true, transformFunction: null }, members: { classPropertyName: "members", publicName: "members", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { filterChange: "filterChange", overlayRequested: "overlayRequested" }, ngImport: i0, template: "<div class=\"c2g-pl-filters\">\n <input\n class=\"c2g-pl-filters__search\"\n type=\"search\"\n [value]=\"filter().query\"\n [placeholder]=\"labels().searchPlaceholderKey\"\n [disabled]=\"disabled()\"\n (input)=\"onQueryChange($event)\" />\n\n <select\n class=\"c2g-pl-filters__visibility\"\n [value]=\"filter().visibility\"\n [disabled]=\"disabled()\"\n (change)=\"onVisibilityChange($event)\">\n <option value=\"all\">{{ labels().showAllVisibilityKey }}</option>\n <option value=\"shared\">{{ labels().sharedVisibilityKey }}</option>\n <option value=\"personal\">{{ labels().personalVisibilityKey }}</option>\n <option value=\"private\">{{ labels().privateVisibilityKey }}</option>\n </select>\n\n <label class=\"c2g-pl-filters__essential\">\n <input\n type=\"checkbox\"\n [checked]=\"filter().essentialsOnly\"\n [disabled]=\"disabled()\"\n (change)=\"onEssentialsOnlyToggle($event)\" />\n <span>{{ labels().essentialsOnlyKey }}</span>\n </label>\n\n <div class=\"c2g-pl-filters__member-filter\">\n <button\n type=\"button\"\n class=\"c2g-pl-filters__member-trigger\"\n [disabled]=\"disabled()\"\n [attr.aria-expanded]=\"membersPanelOpen()\"\n (click)=\"toggleMembersPanel()\">\n <span>{{ memberSelectionLabel() }}</span>\n <span>{{ membersPanelOpen() ? '\u25B4' : '\u25BE' }}</span>\n </button>\n\n @if (membersPanelOpen()) {\n <div class=\"c2g-pl-filters__member-panel\" role=\"dialog\" aria-label=\"User-Filter\">\n <input\n class=\"c2g-pl-filters__member-search\"\n type=\"search\"\n [value]=\"memberQuery()\"\n placeholder=\"User suchen\"\n (input)=\"onMemberSearch($event)\" />\n\n <div class=\"c2g-pl-filters__member-actions\">\n <button type=\"button\" (click)=\"selectAllMembers()\">Alle</button>\n <button type=\"button\" (click)=\"clearMemberSelection()\">Zuruecksetzen</button>\n <button type=\"button\" (click)=\"requestExternalOverlay()\">Externe View</button>\n </div>\n\n <div class=\"c2g-pl-filters__member-list\">\n @for (member of filteredMembers(); track member.id) {\n <label class=\"c2g-pl-filters__member-option\">\n <input\n type=\"checkbox\"\n [checked]=\"isMemberSelected(member.id)\"\n (change)=\"toggleMember(member.id)\" />\n <span>{{ member.name }}</span>\n </label>\n }\n </div>\n </div>\n }\n </div>\n</div>\n", styles: [".c2g-pl-filters{display:grid;gap:.5rem;grid-template-columns:1fr auto auto auto;align-items:center}.c2g-pl-filters__search,.c2g-pl-filters__visibility{min-height:2rem;border:1px solid var(--c2g-color-outline);border-radius:.5rem;padding:.35rem .6rem;background:var(--c2g-color-surface)}.c2g-pl-filters__essential{display:inline-flex;align-items:center;gap:.35rem;font-size:.85rem;color:var(--c2g-color-text-primary)}.c2g-pl-filters__member-filter{position:relative}.c2g-pl-filters__member-trigger{min-height:2rem;border:1px solid var(--c2g-color-outline);border-radius:.5rem;padding:.35rem .6rem;background:var(--c2g-color-surface);color:var(--c2g-color-text-primary);display:inline-flex;gap:.5rem;align-items:center;cursor:pointer}.c2g-pl-filters__member-panel{position:absolute;z-index:20;top:calc(100% + .35rem);right:0;width:min(20rem,80vw);border:1px solid var(--c2g-color-outline);border-radius:.6rem;background:var(--c2g-color-surface);box-shadow:0 10px 26px #142d2124;padding:.55rem;display:grid;gap:.45rem}.c2g-pl-filters__member-search{min-height:1.9rem;border:1px solid var(--c2g-color-outline);border-radius:.45rem;padding:.3rem .55rem}.c2g-pl-filters__member-actions{display:flex;gap:.35rem;flex-wrap:wrap}.c2g-pl-filters__member-actions button{border:1px solid var(--c2g-color-outline);background:var(--c2g-color-neutral-50);border-radius:999px;font-size:.75rem;padding:.18rem .55rem;cursor:pointer}.c2g-pl-filters__member-list{max-height:12rem;overflow:auto;display:grid;gap:.25rem;padding-right:.15rem}.c2g-pl-filters__member-option{display:flex;align-items:center;gap:.45rem;font-size:.82rem;color:var(--c2g-color-text-primary)}@media(max-width:720px){.c2g-pl-filters{grid-template-columns:1fr}.c2g-pl-filters__member-panel{right:auto;left:0;width:100%}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
903
|
+
}
|
|
904
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: PackingListFiltersComponent, decorators: [{
|
|
905
|
+
type: Component,
|
|
906
|
+
args: [{ selector: 'c2g-packing-list-filters', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"c2g-pl-filters\">\n <input\n class=\"c2g-pl-filters__search\"\n type=\"search\"\n [value]=\"filter().query\"\n [placeholder]=\"labels().searchPlaceholderKey\"\n [disabled]=\"disabled()\"\n (input)=\"onQueryChange($event)\" />\n\n <select\n class=\"c2g-pl-filters__visibility\"\n [value]=\"filter().visibility\"\n [disabled]=\"disabled()\"\n (change)=\"onVisibilityChange($event)\">\n <option value=\"all\">{{ labels().showAllVisibilityKey }}</option>\n <option value=\"shared\">{{ labels().sharedVisibilityKey }}</option>\n <option value=\"personal\">{{ labels().personalVisibilityKey }}</option>\n <option value=\"private\">{{ labels().privateVisibilityKey }}</option>\n </select>\n\n <label class=\"c2g-pl-filters__essential\">\n <input\n type=\"checkbox\"\n [checked]=\"filter().essentialsOnly\"\n [disabled]=\"disabled()\"\n (change)=\"onEssentialsOnlyToggle($event)\" />\n <span>{{ labels().essentialsOnlyKey }}</span>\n </label>\n\n <div class=\"c2g-pl-filters__member-filter\">\n <button\n type=\"button\"\n class=\"c2g-pl-filters__member-trigger\"\n [disabled]=\"disabled()\"\n [attr.aria-expanded]=\"membersPanelOpen()\"\n (click)=\"toggleMembersPanel()\">\n <span>{{ memberSelectionLabel() }}</span>\n <span>{{ membersPanelOpen() ? '\u25B4' : '\u25BE' }}</span>\n </button>\n\n @if (membersPanelOpen()) {\n <div class=\"c2g-pl-filters__member-panel\" role=\"dialog\" aria-label=\"User-Filter\">\n <input\n class=\"c2g-pl-filters__member-search\"\n type=\"search\"\n [value]=\"memberQuery()\"\n placeholder=\"User suchen\"\n (input)=\"onMemberSearch($event)\" />\n\n <div class=\"c2g-pl-filters__member-actions\">\n <button type=\"button\" (click)=\"selectAllMembers()\">Alle</button>\n <button type=\"button\" (click)=\"clearMemberSelection()\">Zuruecksetzen</button>\n <button type=\"button\" (click)=\"requestExternalOverlay()\">Externe View</button>\n </div>\n\n <div class=\"c2g-pl-filters__member-list\">\n @for (member of filteredMembers(); track member.id) {\n <label class=\"c2g-pl-filters__member-option\">\n <input\n type=\"checkbox\"\n [checked]=\"isMemberSelected(member.id)\"\n (change)=\"toggleMember(member.id)\" />\n <span>{{ member.name }}</span>\n </label>\n }\n </div>\n </div>\n }\n </div>\n</div>\n", styles: [".c2g-pl-filters{display:grid;gap:.5rem;grid-template-columns:1fr auto auto auto;align-items:center}.c2g-pl-filters__search,.c2g-pl-filters__visibility{min-height:2rem;border:1px solid var(--c2g-color-outline);border-radius:.5rem;padding:.35rem .6rem;background:var(--c2g-color-surface)}.c2g-pl-filters__essential{display:inline-flex;align-items:center;gap:.35rem;font-size:.85rem;color:var(--c2g-color-text-primary)}.c2g-pl-filters__member-filter{position:relative}.c2g-pl-filters__member-trigger{min-height:2rem;border:1px solid var(--c2g-color-outline);border-radius:.5rem;padding:.35rem .6rem;background:var(--c2g-color-surface);color:var(--c2g-color-text-primary);display:inline-flex;gap:.5rem;align-items:center;cursor:pointer}.c2g-pl-filters__member-panel{position:absolute;z-index:20;top:calc(100% + .35rem);right:0;width:min(20rem,80vw);border:1px solid var(--c2g-color-outline);border-radius:.6rem;background:var(--c2g-color-surface);box-shadow:0 10px 26px #142d2124;padding:.55rem;display:grid;gap:.45rem}.c2g-pl-filters__member-search{min-height:1.9rem;border:1px solid var(--c2g-color-outline);border-radius:.45rem;padding:.3rem .55rem}.c2g-pl-filters__member-actions{display:flex;gap:.35rem;flex-wrap:wrap}.c2g-pl-filters__member-actions button{border:1px solid var(--c2g-color-outline);background:var(--c2g-color-neutral-50);border-radius:999px;font-size:.75rem;padding:.18rem .55rem;cursor:pointer}.c2g-pl-filters__member-list{max-height:12rem;overflow:auto;display:grid;gap:.25rem;padding-right:.15rem}.c2g-pl-filters__member-option{display:flex;align-items:center;gap:.45rem;font-size:.82rem;color:var(--c2g-color-text-primary)}@media(max-width:720px){.c2g-pl-filters{grid-template-columns:1fr}.c2g-pl-filters__member-panel{right:auto;left:0;width:100%}}\n"] }]
|
|
907
|
+
}], propDecorators: { filter: [{ type: i0.Input, args: [{ isSignal: true, alias: "filter", required: true }] }], members: [{ type: i0.Input, args: [{ isSignal: true, alias: "members", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], filterChange: [{ type: i0.Output, args: ["filterChange"] }], overlayRequested: [{ type: i0.Output, args: ["overlayRequested"] }] } });
|
|
908
|
+
|
|
909
|
+
class PackingListPrivateListComponent {
|
|
910
|
+
items = input.required(...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
911
|
+
members = input.required(...(ngDevMode ? [{ debugName: "members" }] : []));
|
|
912
|
+
selectedMemberIds = input([], ...(ngDevMode ? [{ debugName: "selectedMemberIds" }] : []));
|
|
913
|
+
currentUserId = input.required(...(ngDevMode ? [{ debugName: "currentUserId" }] : []));
|
|
914
|
+
permissions = input.required(...(ngDevMode ? [{ debugName: "permissions" }] : []));
|
|
915
|
+
labels = input(DEFAULT_PACKING_LIST_LABEL_KEYS, ...(ngDevMode ? [{ debugName: "labels" }] : []));
|
|
916
|
+
privateItems = computed(() => this.items().filter(item => item.visibility === 'private'), ...(ngDevMode ? [{ debugName: "privateItems" }] : []));
|
|
917
|
+
itemChecked = output();
|
|
918
|
+
itemAssigned = output();
|
|
919
|
+
itemDeleted = output();
|
|
920
|
+
itemEditRequested = output();
|
|
921
|
+
personalItemToggled = output();
|
|
922
|
+
memberOverlayRequested = output();
|
|
923
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: PackingListPrivateListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
924
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: PackingListPrivateListComponent, isStandalone: true, selector: "c2g-packing-list-private-list", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, members: { classPropertyName: "members", publicName: "members", isSignal: true, isRequired: true, transformFunction: null }, selectedMemberIds: { classPropertyName: "selectedMemberIds", publicName: "selectedMemberIds", isSignal: true, isRequired: false, transformFunction: null }, currentUserId: { classPropertyName: "currentUserId", publicName: "currentUserId", isSignal: true, isRequired: true, transformFunction: null }, permissions: { classPropertyName: "permissions", publicName: "permissions", isSignal: true, isRequired: true, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemChecked: "itemChecked", itemAssigned: "itemAssigned", itemDeleted: "itemDeleted", itemEditRequested: "itemEditRequested", personalItemToggled: "personalItemToggled", memberOverlayRequested: "memberOverlayRequested" }, ngImport: i0, template: "@if (privateItems().length > 0) {\n <section class=\"c2g-pl-private\">\n <h3>{{ labels().privateSectionTitleKey }}</h3>\n <div class=\"c2g-pl-private__items\">\n @for (item of privateItems(); track item.id) {\n <c2g-packing-list-item\n [item]=\"item\"\n [members]=\"members()\"\n [selectedMemberIds]=\"selectedMemberIds()\"\n [currentUserId]=\"currentUserId()\"\n [permissions]=\"permissions()\"\n (itemChecked)=\"itemChecked.emit($event)\"\n (itemAssigned)=\"itemAssigned.emit($event)\"\n (itemDeleted)=\"itemDeleted.emit($event)\"\n (itemEditRequested)=\"itemEditRequested.emit($event)\"\n (personalItemToggled)=\"personalItemToggled.emit($event)\"\n (memberOverlayRequested)=\"memberOverlayRequested.emit($event)\">\n </c2g-packing-list-item>\n }\n </div>\n </section>\n}\n", styles: [".c2g-pl-private{display:grid;gap:.5rem}.c2g-pl-private h3{margin:0;font-size:.95rem;color:var(--c2g-color-text-primary)}.c2g-pl-private__items{display:grid;gap:.55rem}\n"], dependencies: [{ kind: "component", type: PackingListItemComponent, selector: "c2g-packing-list-item", inputs: ["item", "members", "currentUserId", "permissions", "selectedMemberIds"], outputs: ["itemChecked", "itemAssigned", "itemDeleted", "itemEditRequested", "personalItemToggled", "memberOverlayRequested"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
925
|
+
}
|
|
926
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: PackingListPrivateListComponent, decorators: [{
|
|
927
|
+
type: Component,
|
|
928
|
+
args: [{ selector: 'c2g-packing-list-private-list', standalone: true, imports: [PackingListItemComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (privateItems().length > 0) {\n <section class=\"c2g-pl-private\">\n <h3>{{ labels().privateSectionTitleKey }}</h3>\n <div class=\"c2g-pl-private__items\">\n @for (item of privateItems(); track item.id) {\n <c2g-packing-list-item\n [item]=\"item\"\n [members]=\"members()\"\n [selectedMemberIds]=\"selectedMemberIds()\"\n [currentUserId]=\"currentUserId()\"\n [permissions]=\"permissions()\"\n (itemChecked)=\"itemChecked.emit($event)\"\n (itemAssigned)=\"itemAssigned.emit($event)\"\n (itemDeleted)=\"itemDeleted.emit($event)\"\n (itemEditRequested)=\"itemEditRequested.emit($event)\"\n (personalItemToggled)=\"personalItemToggled.emit($event)\"\n (memberOverlayRequested)=\"memberOverlayRequested.emit($event)\">\n </c2g-packing-list-item>\n }\n </div>\n </section>\n}\n", styles: [".c2g-pl-private{display:grid;gap:.5rem}.c2g-pl-private h3{margin:0;font-size:.95rem;color:var(--c2g-color-text-primary)}.c2g-pl-private__items{display:grid;gap:.55rem}\n"] }]
|
|
929
|
+
}], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], members: [{ type: i0.Input, args: [{ isSignal: true, alias: "members", required: true }] }], selectedMemberIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedMemberIds", required: false }] }], currentUserId: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentUserId", required: true }] }], permissions: [{ type: i0.Input, args: [{ isSignal: true, alias: "permissions", required: true }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], itemChecked: [{ type: i0.Output, args: ["itemChecked"] }], itemAssigned: [{ type: i0.Output, args: ["itemAssigned"] }], itemDeleted: [{ type: i0.Output, args: ["itemDeleted"] }], itemEditRequested: [{ type: i0.Output, args: ["itemEditRequested"] }], personalItemToggled: [{ type: i0.Output, args: ["personalItemToggled"] }], memberOverlayRequested: [{ type: i0.Output, args: ["memberOverlayRequested"] }] } });
|
|
930
|
+
|
|
931
|
+
class PackingListItemCreateComponent {
|
|
932
|
+
permissions = input({
|
|
933
|
+
isOrganizer: false,
|
|
934
|
+
isGuest: false,
|
|
935
|
+
isSolo: false,
|
|
936
|
+
readOnly: false
|
|
937
|
+
}, ...(ngDevMode ? [{ debugName: "permissions" }] : []));
|
|
938
|
+
labels = input(DEFAULT_PACKING_LIST_LABEL_KEYS, ...(ngDevMode ? [{ debugName: "labels" }] : []));
|
|
939
|
+
readOnly = input(false, ...(ngDevMode ? [{ debugName: "readOnly" }] : []));
|
|
940
|
+
categories = input(Object.values(C2G_PACKING_CATEGORY_INFO), ...(ngDevMode ? [{ debugName: "categories" }] : []));
|
|
941
|
+
itemCreated = output();
|
|
942
|
+
dialogOpen = signal(false, ...(ngDevMode ? [{ debugName: "dialogOpen" }] : []));
|
|
943
|
+
formData = signal({
|
|
944
|
+
name: '',
|
|
945
|
+
category: Object.values(C2G_PACKING_CATEGORY_INFO)[0]?.key ?? 'other',
|
|
946
|
+
visibility: 'private',
|
|
947
|
+
essential: false,
|
|
948
|
+
quantity: 1,
|
|
949
|
+
weightKg: undefined,
|
|
950
|
+
packedVolumeL: undefined,
|
|
951
|
+
hint: ''
|
|
952
|
+
}, ...(ngDevMode ? [{ debugName: "formData" }] : []));
|
|
953
|
+
availableVisibilities = computed(() => {
|
|
954
|
+
const perms = this.permissions();
|
|
955
|
+
const labels = this.labels();
|
|
956
|
+
const visibilities = [];
|
|
957
|
+
// Private is always available
|
|
958
|
+
visibilities.push({
|
|
959
|
+
value: 'private',
|
|
960
|
+
label: labels.privateVisibilityKey ?? 'Private'
|
|
961
|
+
});
|
|
962
|
+
// Personal is available if not a guest
|
|
963
|
+
if (!perms.isGuest) {
|
|
964
|
+
visibilities.push({
|
|
965
|
+
value: 'personal',
|
|
966
|
+
label: labels.personalVisibilityKey ?? 'Personal'
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
// Shared is available for organizers or if tour allows members
|
|
970
|
+
if (perms.isOrganizer || perms.canMembersAddSharedItems) {
|
|
971
|
+
visibilities.push({
|
|
972
|
+
value: 'shared',
|
|
973
|
+
label: labels.sharedVisibilityKey ?? 'Shared'
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
return visibilities;
|
|
977
|
+
}, ...(ngDevMode ? [{ debugName: "availableVisibilities" }] : []));
|
|
978
|
+
categoryOptions = computed(() => {
|
|
979
|
+
return this.categories().map(cat => ({
|
|
980
|
+
value: cat.key,
|
|
981
|
+
label: cat.label || cat.key
|
|
982
|
+
}));
|
|
983
|
+
}, ...(ngDevMode ? [{ debugName: "categoryOptions" }] : []));
|
|
984
|
+
openDialog() {
|
|
985
|
+
this.dialogOpen.set(true);
|
|
986
|
+
}
|
|
987
|
+
closeDialog() {
|
|
988
|
+
this.dialogOpen.set(false);
|
|
989
|
+
this.resetForm();
|
|
990
|
+
}
|
|
991
|
+
resetForm() {
|
|
992
|
+
this.formData.set({
|
|
993
|
+
name: '',
|
|
994
|
+
category: Object.values(C2G_PACKING_CATEGORY_INFO)[0]?.key ?? 'other',
|
|
995
|
+
visibility: this.availableVisibilities()[0]?.value ?? 'private',
|
|
996
|
+
essential: false,
|
|
997
|
+
quantity: 1,
|
|
998
|
+
weightKg: undefined,
|
|
999
|
+
packedVolumeL: undefined,
|
|
1000
|
+
hint: ''
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
submit() {
|
|
1004
|
+
const data = this.formData();
|
|
1005
|
+
if (!data.name.trim()) {
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
const newItem = {
|
|
1009
|
+
name: data.name.trim(),
|
|
1010
|
+
category: data.category,
|
|
1011
|
+
visibility: data.visibility,
|
|
1012
|
+
essential: data.essential,
|
|
1013
|
+
quantity: data.quantity > 0 ? data.quantity : undefined,
|
|
1014
|
+
weightKg: data.weightKg && data.weightKg > 0 ? data.weightKg : undefined,
|
|
1015
|
+
packedVolumeL: data.packedVolumeL && data.packedVolumeL > 0 ? data.packedVolumeL : undefined,
|
|
1016
|
+
hint: data.hint.trim() || undefined
|
|
1017
|
+
};
|
|
1018
|
+
this.itemCreated.emit(newItem);
|
|
1019
|
+
this.closeDialog();
|
|
1020
|
+
}
|
|
1021
|
+
updateVisibility(visibility) {
|
|
1022
|
+
this.formData.update(data => ({ ...data, visibility }));
|
|
1023
|
+
}
|
|
1024
|
+
getCategoryLabel(categoryKey) {
|
|
1025
|
+
return this.categories().find(c => c.key === categoryKey)?.label ?? categoryKey;
|
|
1026
|
+
}
|
|
1027
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: PackingListItemCreateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1028
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: PackingListItemCreateComponent, isStandalone: true, selector: "c2g-packing-list-item-create", inputs: { permissions: { classPropertyName: "permissions", publicName: "permissions", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null }, readOnly: { classPropertyName: "readOnly", publicName: "readOnly", isSignal: true, isRequired: false, transformFunction: null }, categories: { classPropertyName: "categories", publicName: "categories", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemCreated: "itemCreated" }, ngImport: i0, template: "<c2g-button\n variant=\"secondary\"\n size=\"md\"\n icon=\"+\"\n [disabled]=\"readOnly()\"\n (clicked)=\"openDialog()\">\n {{ labels().addItemKey || 'Item hinzuf\u00FCgen' }}\n</c2g-button>\n\n@if (dialogOpen()) {\n <div class=\"c2g-pl-item-create__overlay\" (click)=\"closeDialog()\"></div>\n <dialog class=\"c2g-pl-item-create__dialog\" open>\n <div class=\"c2g-pl-item-create__header\">\n <h2>{{ labels().createItemKey || 'Neues Item' }}</h2>\n <c2g-button\n variant=\"icon\"\n size=\"sm\"\n icon=\"\u2715\"\n aria-label=\"Schlie\u00DFen\"\n (clicked)=\"closeDialog()\">\n </c2g-button>\n </div>\n\n <form class=\"c2g-pl-item-create__form\" (ngSubmit)=\"submit()\">\n <!-- Name Input -->\n <div class=\"c2g-pl-item-create__field\">\n <c2g-input\n [label]=\"labels().itemNameKey || 'Name'\"\n [required]=\"true\"\n [(ngModel)]=\"formData().name\"\n [ngModelOptions]=\"{ updateOn: 'blur' }\"\n name=\"name\"\n placeholder=\"z.B. Zelt, Schlafsack...\">\n </c2g-input>\n </div>\n\n <!-- Category Select -->\n <div class=\"c2g-pl-item-create__field\">\n <c2g-select\n [label]=\"(labels().categoryKey || 'Kategorie') + ' *'\"\n [options]=\"categoryOptions()\"\n [required]=\"true\"\n [(ngModel)]=\"formData().category\"\n name=\"category\">\n </c2g-select>\n </div>\n\n <!-- Visibility Radio -->\n @if (availableVisibilities().length > 1) {\n <div class=\"c2g-pl-item-create__field\">\n <c2g-radio-group\n [label]=\"(labels().visibilityKey || 'Sichtbarkeit') + ' *'\"\n [options]=\"availableVisibilities()\"\n [(ngModel)]=\"formData().visibility\"\n name=\"visibility\"\n orientation=\"vertical\">\n </c2g-radio-group>\n </div>\n }\n\n <!-- Essential Checkbox -->\n <div class=\"c2g-pl-item-create__field\">\n <c2g-checkbox\n [label]=\"labels().essentialKey || 'Essentiell'\"\n [(ngModel)]=\"formData().essential\"\n name=\"essential\">\n </c2g-checkbox>\n </div>\n\n <!-- Quantity -->\n <div class=\"c2g-pl-item-create__field\">\n <c2g-input\n [label]=\"labels().quantityKey || 'Menge'\"\n [(ngModel)]=\"formData().quantity\"\n name=\"quantity\"\n placeholder=\"z.B. 1\">\n </c2g-input>\n </div>\n\n <!-- Weight -->\n <div class=\"c2g-pl-item-create__field\">\n <c2g-input\n [label]=\"labels().weightKey || 'Gewicht (kg)'\"\n [(ngModel)]=\"formData().weightKg\"\n name=\"weightKg\"\n placeholder=\"z.B. 0.5\">\n </c2g-input>\n </div>\n\n <!-- Volume -->\n <div class=\"c2g-pl-item-create__field\">\n <c2g-input\n [label]=\"labels().volumeKey || 'Volumen (l)'\"\n [(ngModel)]=\"formData().packedVolumeL\"\n name=\"packedVolumeL\"\n placeholder=\"z.B. 1.5\">\n </c2g-input>\n </div>\n\n <!-- Hint -->\n <div class=\"c2g-pl-item-create__field\">\n <c2g-textarea\n [label]=\"labels().hintKey || 'Notiz'\"\n [(ngModel)]=\"formData().hint\"\n name=\"hint\"\n [rows]=\"2\"\n placeholder=\"z.B. Schlafsack f\u00FCr Kinder, Wetterschutz...\">\n </c2g-textarea>\n </div>\n\n <!-- Buttons -->\n <div class=\"c2g-pl-item-create__actions\">\n <c2g-button\n type=\"button\"\n variant=\"secondary\"\n size=\"md\"\n (clicked)=\"closeDialog()\">\n {{ labels().cancelKey || 'Abbrechen' }}\n </c2g-button>\n <c2g-button\n type=\"submit\"\n variant=\"primary\"\n size=\"md\"\n [disabled]=\"!formData().name.trim()\">\n {{ labels().createKey || 'Erstellen' }}\n </c2g-button>\n </div>\n </form>\n </dialog>\n}\n", styles: [".c2g-pl-item-create__btn{border:none;background:var(--c2g-color-surface);color:var(--c2g-color-text-primary);border-radius:.5rem;padding:.45rem .9rem;font-size:.9rem;font-weight:500;cursor:pointer;display:inline-flex;align-items:center;gap:.4rem;border:1px solid var(--c2g-color-outline);transition:all .2s ease}.c2g-pl-item-create__btn:hover:not(:disabled){background:var(--c2g-color-neutral-100);border-color:var(--c2g-color-text-primary)}.c2g-pl-item-create__btn:disabled{opacity:.5;cursor:not-allowed}.c2g-pl-item-create__icon{font-size:1.2rem;font-weight:600;line-height:1}.c2g-pl-item-create__overlay{position:fixed;inset:0;background:#152b2166;z-index:99}.c2g-pl-item-create__dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:100;border:none;border-radius:.75rem;background:var(--c2g-color-surface);box-shadow:0 20px 40px #152b2133;padding:1.5rem;max-width:500px;width:90%;max-height:90vh;overflow-y:auto}@media(max-width:600px){.c2g-pl-item-create__dialog{width:95%;padding:1.2rem}}.c2g-pl-item-create__header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1.5rem}.c2g-pl-item-create__header h2{margin:0;font-size:1.3rem;font-weight:600;color:var(--c2g-color-text-primary)}.c2g-pl-item-create__close{border:none;background:transparent;color:var(--c2g-color-text-muted);font-size:1.8rem;line-height:1;padding:0;cursor:pointer;width:2rem;height:2rem;display:flex;align-items:center;justify-content:center;transition:color .2s ease}.c2g-pl-item-create__close:hover{color:var(--c2g-color-text-primary)}.c2g-pl-item-create__form{display:grid;gap:1rem}.c2g-pl-item-create__field{display:grid;gap:.4rem}.c2g-pl-item-create__field label{font-weight:500;color:var(--c2g-color-text-primary);font-size:.9rem}.c2g-pl-item-create__field input[type=text],.c2g-pl-item-create__field input[type=number],.c2g-pl-item-create__field textarea,.c2g-pl-item-create__field select{border:1px solid var(--c2g-color-outline-variant);border-radius:.4rem;padding:.5rem .6rem;font-size:.9rem;font-family:inherit;color:var(--c2g-color-text-primary);background:var(--c2g-color-surface);transition:border-color .2s ease}.c2g-pl-item-create__field input[type=text]:focus,.c2g-pl-item-create__field input[type=number]:focus,.c2g-pl-item-create__field textarea:focus,.c2g-pl-item-create__field select:focus{outline:none;border-color:var(--c2g-color-text-primary);box-shadow:0 0 0 2px var(--c2g-color-border-soft)}.c2g-pl-item-create__field input[type=text]::placeholder,.c2g-pl-item-create__field input[type=number]::placeholder,.c2g-pl-item-create__field textarea::placeholder,.c2g-pl-item-create__field select::placeholder{color:var(--c2g-color-text-muted)}.c2g-pl-item-create__field textarea{resize:vertical;line-height:1.4}.c2g-pl-item-create__field select{cursor:pointer}.c2g-pl-item-create__checkbox-label{display:flex;align-items:center;gap:.5rem;cursor:pointer;-webkit-user-select:none;user-select:none}.c2g-pl-item-create__checkbox-label input[type=checkbox]{width:1.1rem;height:1.1rem;cursor:pointer}.c2g-pl-item-create__checkbox-label span{color:var(--c2g-color-text-primary);font-size:.9rem}.c2g-pl-item-create__visibility-options{display:flex;flex-direction:column;gap:.5rem}.c2g-pl-item-create__visibility-label{display:flex;align-items:center;gap:.5rem;cursor:pointer;-webkit-user-select:none;user-select:none}.c2g-pl-item-create__visibility-label input[type=radio]{width:1.1rem;height:1.1rem;cursor:pointer}.c2g-pl-item-create__visibility-label span{color:var(--c2g-color-text-primary);font-size:.9rem}.c2g-pl-item-create__actions{display:flex;gap:.8rem;margin-top:1.5rem;justify-content:flex-end}.c2g-pl-item-create__btn-primary{border:none;background:var(--c2g-color-text-primary);color:var(--c2g-color-surface);border-radius:.4rem;padding:.6rem 1.2rem;font-size:.9rem;font-weight:500;cursor:pointer;transition:all .2s ease}.c2g-pl-item-create__btn-primary:hover:not(:disabled){background:var(--c2g-color-text-secondary)}.c2g-pl-item-create__btn-primary:disabled{opacity:.5;cursor:not-allowed}.c2g-pl-item-create__btn-secondary{border:1px solid var(--c2g-color-outline);background:var(--c2g-color-surface);color:var(--c2g-color-text-primary);border-radius:.4rem;padding:.6rem 1.2rem;font-size:.9rem;font-weight:500;cursor:pointer;transition:all .2s ease}.c2g-pl-item-create__btn-secondary:hover{background:var(--c2g-color-neutral-100);border-color:var(--c2g-color-text-primary)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: ButtonComponent, selector: "c2g-button", inputs: ["variant", "size", "disabled", "loading", "icon", "iconPosition", "iconOnly", "ariaLabel", "loadingAriaLabel", "type"], outputs: ["clicked"] }, { kind: "component", type: RadioGroupComponent, selector: "c2g-radio-group", inputs: ["label", "hint", "disabled", "orientation", "options", "name"], outputs: ["valueChange"] }, { kind: "component", type: CheckboxComponent, selector: "c2g-checkbox", inputs: ["id", "label", "hint", "disabled"], outputs: ["checkedChange"] }, { kind: "component", type: SelectComponent, selector: "c2g-select", inputs: ["label", "placeholder", "hint", "error", "options", "disabled", "required", "id"], outputs: ["valueChanged"] }, { kind: "component", type: InputComponent, selector: "c2g-input", inputs: ["label", "hint", "error", "placeholder", "prefix", "suffix", "loading", "disabled", "required", "maxLength", "id", "type"], outputs: ["valueChanged"] }, { kind: "component", type: TextareaComponent, selector: "c2g-textarea", inputs: ["label", "hint", "error", "placeholder", "prefix", "suffix", "loading", "disabled", "required", "maxLength", "rows", "id"], outputs: ["valueChanged"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1029
|
+
}
|
|
1030
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: PackingListItemCreateComponent, decorators: [{
|
|
1031
|
+
type: Component,
|
|
1032
|
+
args: [{ selector: 'c2g-packing-list-item-create', standalone: true, imports: [CommonModule, FormsModule, ButtonComponent, RadioGroupComponent, CheckboxComponent, SelectComponent, InputComponent, TextareaComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<c2g-button\n variant=\"secondary\"\n size=\"md\"\n icon=\"+\"\n [disabled]=\"readOnly()\"\n (clicked)=\"openDialog()\">\n {{ labels().addItemKey || 'Item hinzuf\u00FCgen' }}\n</c2g-button>\n\n@if (dialogOpen()) {\n <div class=\"c2g-pl-item-create__overlay\" (click)=\"closeDialog()\"></div>\n <dialog class=\"c2g-pl-item-create__dialog\" open>\n <div class=\"c2g-pl-item-create__header\">\n <h2>{{ labels().createItemKey || 'Neues Item' }}</h2>\n <c2g-button\n variant=\"icon\"\n size=\"sm\"\n icon=\"\u2715\"\n aria-label=\"Schlie\u00DFen\"\n (clicked)=\"closeDialog()\">\n </c2g-button>\n </div>\n\n <form class=\"c2g-pl-item-create__form\" (ngSubmit)=\"submit()\">\n <!-- Name Input -->\n <div class=\"c2g-pl-item-create__field\">\n <c2g-input\n [label]=\"labels().itemNameKey || 'Name'\"\n [required]=\"true\"\n [(ngModel)]=\"formData().name\"\n [ngModelOptions]=\"{ updateOn: 'blur' }\"\n name=\"name\"\n placeholder=\"z.B. Zelt, Schlafsack...\">\n </c2g-input>\n </div>\n\n <!-- Category Select -->\n <div class=\"c2g-pl-item-create__field\">\n <c2g-select\n [label]=\"(labels().categoryKey || 'Kategorie') + ' *'\"\n [options]=\"categoryOptions()\"\n [required]=\"true\"\n [(ngModel)]=\"formData().category\"\n name=\"category\">\n </c2g-select>\n </div>\n\n <!-- Visibility Radio -->\n @if (availableVisibilities().length > 1) {\n <div class=\"c2g-pl-item-create__field\">\n <c2g-radio-group\n [label]=\"(labels().visibilityKey || 'Sichtbarkeit') + ' *'\"\n [options]=\"availableVisibilities()\"\n [(ngModel)]=\"formData().visibility\"\n name=\"visibility\"\n orientation=\"vertical\">\n </c2g-radio-group>\n </div>\n }\n\n <!-- Essential Checkbox -->\n <div class=\"c2g-pl-item-create__field\">\n <c2g-checkbox\n [label]=\"labels().essentialKey || 'Essentiell'\"\n [(ngModel)]=\"formData().essential\"\n name=\"essential\">\n </c2g-checkbox>\n </div>\n\n <!-- Quantity -->\n <div class=\"c2g-pl-item-create__field\">\n <c2g-input\n [label]=\"labels().quantityKey || 'Menge'\"\n [(ngModel)]=\"formData().quantity\"\n name=\"quantity\"\n placeholder=\"z.B. 1\">\n </c2g-input>\n </div>\n\n <!-- Weight -->\n <div class=\"c2g-pl-item-create__field\">\n <c2g-input\n [label]=\"labels().weightKey || 'Gewicht (kg)'\"\n [(ngModel)]=\"formData().weightKg\"\n name=\"weightKg\"\n placeholder=\"z.B. 0.5\">\n </c2g-input>\n </div>\n\n <!-- Volume -->\n <div class=\"c2g-pl-item-create__field\">\n <c2g-input\n [label]=\"labels().volumeKey || 'Volumen (l)'\"\n [(ngModel)]=\"formData().packedVolumeL\"\n name=\"packedVolumeL\"\n placeholder=\"z.B. 1.5\">\n </c2g-input>\n </div>\n\n <!-- Hint -->\n <div class=\"c2g-pl-item-create__field\">\n <c2g-textarea\n [label]=\"labels().hintKey || 'Notiz'\"\n [(ngModel)]=\"formData().hint\"\n name=\"hint\"\n [rows]=\"2\"\n placeholder=\"z.B. Schlafsack f\u00FCr Kinder, Wetterschutz...\">\n </c2g-textarea>\n </div>\n\n <!-- Buttons -->\n <div class=\"c2g-pl-item-create__actions\">\n <c2g-button\n type=\"button\"\n variant=\"secondary\"\n size=\"md\"\n (clicked)=\"closeDialog()\">\n {{ labels().cancelKey || 'Abbrechen' }}\n </c2g-button>\n <c2g-button\n type=\"submit\"\n variant=\"primary\"\n size=\"md\"\n [disabled]=\"!formData().name.trim()\">\n {{ labels().createKey || 'Erstellen' }}\n </c2g-button>\n </div>\n </form>\n </dialog>\n}\n", styles: [".c2g-pl-item-create__btn{border:none;background:var(--c2g-color-surface);color:var(--c2g-color-text-primary);border-radius:.5rem;padding:.45rem .9rem;font-size:.9rem;font-weight:500;cursor:pointer;display:inline-flex;align-items:center;gap:.4rem;border:1px solid var(--c2g-color-outline);transition:all .2s ease}.c2g-pl-item-create__btn:hover:not(:disabled){background:var(--c2g-color-neutral-100);border-color:var(--c2g-color-text-primary)}.c2g-pl-item-create__btn:disabled{opacity:.5;cursor:not-allowed}.c2g-pl-item-create__icon{font-size:1.2rem;font-weight:600;line-height:1}.c2g-pl-item-create__overlay{position:fixed;inset:0;background:#152b2166;z-index:99}.c2g-pl-item-create__dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:100;border:none;border-radius:.75rem;background:var(--c2g-color-surface);box-shadow:0 20px 40px #152b2133;padding:1.5rem;max-width:500px;width:90%;max-height:90vh;overflow-y:auto}@media(max-width:600px){.c2g-pl-item-create__dialog{width:95%;padding:1.2rem}}.c2g-pl-item-create__header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1.5rem}.c2g-pl-item-create__header h2{margin:0;font-size:1.3rem;font-weight:600;color:var(--c2g-color-text-primary)}.c2g-pl-item-create__close{border:none;background:transparent;color:var(--c2g-color-text-muted);font-size:1.8rem;line-height:1;padding:0;cursor:pointer;width:2rem;height:2rem;display:flex;align-items:center;justify-content:center;transition:color .2s ease}.c2g-pl-item-create__close:hover{color:var(--c2g-color-text-primary)}.c2g-pl-item-create__form{display:grid;gap:1rem}.c2g-pl-item-create__field{display:grid;gap:.4rem}.c2g-pl-item-create__field label{font-weight:500;color:var(--c2g-color-text-primary);font-size:.9rem}.c2g-pl-item-create__field input[type=text],.c2g-pl-item-create__field input[type=number],.c2g-pl-item-create__field textarea,.c2g-pl-item-create__field select{border:1px solid var(--c2g-color-outline-variant);border-radius:.4rem;padding:.5rem .6rem;font-size:.9rem;font-family:inherit;color:var(--c2g-color-text-primary);background:var(--c2g-color-surface);transition:border-color .2s ease}.c2g-pl-item-create__field input[type=text]:focus,.c2g-pl-item-create__field input[type=number]:focus,.c2g-pl-item-create__field textarea:focus,.c2g-pl-item-create__field select:focus{outline:none;border-color:var(--c2g-color-text-primary);box-shadow:0 0 0 2px var(--c2g-color-border-soft)}.c2g-pl-item-create__field input[type=text]::placeholder,.c2g-pl-item-create__field input[type=number]::placeholder,.c2g-pl-item-create__field textarea::placeholder,.c2g-pl-item-create__field select::placeholder{color:var(--c2g-color-text-muted)}.c2g-pl-item-create__field textarea{resize:vertical;line-height:1.4}.c2g-pl-item-create__field select{cursor:pointer}.c2g-pl-item-create__checkbox-label{display:flex;align-items:center;gap:.5rem;cursor:pointer;-webkit-user-select:none;user-select:none}.c2g-pl-item-create__checkbox-label input[type=checkbox]{width:1.1rem;height:1.1rem;cursor:pointer}.c2g-pl-item-create__checkbox-label span{color:var(--c2g-color-text-primary);font-size:.9rem}.c2g-pl-item-create__visibility-options{display:flex;flex-direction:column;gap:.5rem}.c2g-pl-item-create__visibility-label{display:flex;align-items:center;gap:.5rem;cursor:pointer;-webkit-user-select:none;user-select:none}.c2g-pl-item-create__visibility-label input[type=radio]{width:1.1rem;height:1.1rem;cursor:pointer}.c2g-pl-item-create__visibility-label span{color:var(--c2g-color-text-primary);font-size:.9rem}.c2g-pl-item-create__actions{display:flex;gap:.8rem;margin-top:1.5rem;justify-content:flex-end}.c2g-pl-item-create__btn-primary{border:none;background:var(--c2g-color-text-primary);color:var(--c2g-color-surface);border-radius:.4rem;padding:.6rem 1.2rem;font-size:.9rem;font-weight:500;cursor:pointer;transition:all .2s ease}.c2g-pl-item-create__btn-primary:hover:not(:disabled){background:var(--c2g-color-text-secondary)}.c2g-pl-item-create__btn-primary:disabled{opacity:.5;cursor:not-allowed}.c2g-pl-item-create__btn-secondary{border:1px solid var(--c2g-color-outline);background:var(--c2g-color-surface);color:var(--c2g-color-text-primary);border-radius:.4rem;padding:.6rem 1.2rem;font-size:.9rem;font-weight:500;cursor:pointer;transition:all .2s ease}.c2g-pl-item-create__btn-secondary:hover{background:var(--c2g-color-neutral-100);border-color:var(--c2g-color-text-primary)}\n"] }]
|
|
1033
|
+
}], propDecorators: { permissions: [{ type: i0.Input, args: [{ isSignal: true, alias: "permissions", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], readOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readOnly", required: false }] }], categories: [{ type: i0.Input, args: [{ isSignal: true, alias: "categories", required: false }] }], itemCreated: [{ type: i0.Output, args: ["itemCreated"] }] } });
|
|
1034
|
+
|
|
1035
|
+
class PackingListComponent {
|
|
1036
|
+
items = input.required(...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
1037
|
+
members = input.required(...(ngDevMode ? [{ debugName: "members" }] : []));
|
|
1038
|
+
currentUserId = input.required(...(ngDevMode ? [{ debugName: "currentUserId" }] : []));
|
|
1039
|
+
permissions = input({
|
|
1040
|
+
isOrganizer: false,
|
|
1041
|
+
isGuest: false,
|
|
1042
|
+
isSolo: false,
|
|
1043
|
+
readOnly: false
|
|
1044
|
+
}, ...(ngDevMode ? [{ debugName: "permissions" }] : []));
|
|
1045
|
+
labels = input(DEFAULT_PACKING_LIST_LABEL_KEYS, ...(ngDevMode ? [{ debugName: "labels" }] : []));
|
|
1046
|
+
categories = input(Object.values(C2G_PACKING_CATEGORY_INFO), ...(ngDevMode ? [{ debugName: "categories" }] : []));
|
|
1047
|
+
config = input(DEFAULT_PACKING_LIST_CONFIG, ...(ngDevMode ? [{ debugName: "config" }] : []));
|
|
1048
|
+
// Compat layer for deprecated inputs (showStats, showPrivateAsSeparateList)
|
|
1049
|
+
showPrivateAsSeparateList = input(false, ...(ngDevMode ? [{ debugName: "showPrivateAsSeparateList" }] : []));
|
|
1050
|
+
showStats = input(true, ...(ngDevMode ? [{ debugName: "showStats" }] : []));
|
|
1051
|
+
itemChecked = output();
|
|
1052
|
+
itemAssigned = output();
|
|
1053
|
+
itemDeleted = output();
|
|
1054
|
+
itemEditRequested = output();
|
|
1055
|
+
itemCreated = output();
|
|
1056
|
+
personalItemToggled = output();
|
|
1057
|
+
memberOverlayRequested = output();
|
|
1058
|
+
externalMemberViewRequested = output();
|
|
1059
|
+
filterChanged = output();
|
|
1060
|
+
sortChanged = output();
|
|
1061
|
+
rowActionTriggered = output();
|
|
1062
|
+
// Merged config: config input takes precedence over deprecated inputs
|
|
1063
|
+
mergedConfig = computed(() => {
|
|
1064
|
+
const cfg = this.config();
|
|
1065
|
+
const features = cfg.features ?? {};
|
|
1066
|
+
// Apply deprecated inputs if config doesn't override
|
|
1067
|
+
return {
|
|
1068
|
+
...cfg,
|
|
1069
|
+
features: {
|
|
1070
|
+
...features,
|
|
1071
|
+
stats: features.stats !== undefined ? features.stats : this.showStats(),
|
|
1072
|
+
privateSection: features.privateSection !== undefined ? features.privateSection : this.showPrivateAsSeparateList(),
|
|
1073
|
+
}
|
|
1074
|
+
};
|
|
1075
|
+
}, ...(ngDevMode ? [{ debugName: "mergedConfig" }] : []));
|
|
1076
|
+
filter = signal({
|
|
1077
|
+
query: '',
|
|
1078
|
+
visibility: 'all',
|
|
1079
|
+
essentialsOnly: false,
|
|
1080
|
+
memberIds: []
|
|
1081
|
+
}, ...(ngDevMode ? [{ debugName: "filter" }] : []));
|
|
1082
|
+
expandedCategories = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedCategories" }] : []));
|
|
1083
|
+
hasUserToggled = signal(false, ...(ngDevMode ? [{ debugName: "hasUserToggled" }] : []));
|
|
1084
|
+
filteredItems = computed(() => {
|
|
1085
|
+
const filter = this.filter();
|
|
1086
|
+
const q = filter.query.trim().toLowerCase();
|
|
1087
|
+
return this.items().filter(item => {
|
|
1088
|
+
if (filter.visibility !== 'all' && item.visibility !== filter.visibility) {
|
|
1089
|
+
return false;
|
|
1090
|
+
}
|
|
1091
|
+
if (filter.essentialsOnly && !item.essential) {
|
|
1092
|
+
return false;
|
|
1093
|
+
}
|
|
1094
|
+
if (filter.memberIds.length > 0) {
|
|
1095
|
+
const selected = new Set(filter.memberIds);
|
|
1096
|
+
const assignments = item.assignments ?? [];
|
|
1097
|
+
const hasSelectedAssignment = assignments.some(assignment => selected.has(assignment.memberId));
|
|
1098
|
+
if (!hasSelectedAssignment) {
|
|
1099
|
+
return false;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
if (!q) {
|
|
1103
|
+
return true;
|
|
1104
|
+
}
|
|
1105
|
+
const searchable = `${item.name} ${item.hint ?? ''} ${item.reason ?? ''}`.toLowerCase();
|
|
1106
|
+
return searchable.includes(q);
|
|
1107
|
+
});
|
|
1108
|
+
}, ...(ngDevMode ? [{ debugName: "filteredItems" }] : []));
|
|
1109
|
+
categoryGroups = computed(() => {
|
|
1110
|
+
const grouped = new Map();
|
|
1111
|
+
for (const item of this.filteredItems()) {
|
|
1112
|
+
const key = item.category || 'other';
|
|
1113
|
+
if (!grouped.has(key)) {
|
|
1114
|
+
grouped.set(key, []);
|
|
1115
|
+
}
|
|
1116
|
+
grouped.get(key).push(item);
|
|
1117
|
+
}
|
|
1118
|
+
// Category order: follow C2G_PACKING_CATEGORY_INFO definition, unknowns at end.
|
|
1119
|
+
// A category sinks to the bottom only when ALL its items are packed/confirmed.
|
|
1120
|
+
const knownOrder = Object.keys(C2G_PACKING_CATEGORY_INFO);
|
|
1121
|
+
const isPacked = (i) => !!i.assignments?.some(a => a.status === 'packed' || a.status === 'confirmed');
|
|
1122
|
+
const entries = Array.from(grouped.entries());
|
|
1123
|
+
entries.sort(([a, aItems], [b, bItems]) => {
|
|
1124
|
+
const aDone = aItems.every(isPacked) ? 1 : 0;
|
|
1125
|
+
const bDone = bItems.every(isPacked) ? 1 : 0;
|
|
1126
|
+
if (aDone !== bDone)
|
|
1127
|
+
return aDone - bDone;
|
|
1128
|
+
const ai = knownOrder.indexOf(a);
|
|
1129
|
+
const bi = knownOrder.indexOf(b);
|
|
1130
|
+
return (ai === -1 ? knownOrder.length : ai) - (bi === -1 ? knownOrder.length : bi);
|
|
1131
|
+
});
|
|
1132
|
+
return entries.map(([key, items]) => ({ key, items }));
|
|
1133
|
+
}, ...(ngDevMode ? [{ debugName: "categoryGroups" }] : []));
|
|
1134
|
+
constructor() {
|
|
1135
|
+
effect(() => {
|
|
1136
|
+
if (this.hasUserToggled()) {
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
const defaults = this.categoryGroups().slice(0, 3).map(group => group.key);
|
|
1140
|
+
if (defaults.length === 0) {
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
if (this.expandedCategories().size === 0) {
|
|
1144
|
+
this.expandedCategories.set(new Set(defaults));
|
|
1145
|
+
}
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
onFilterChange(next) {
|
|
1149
|
+
this.filter.set(next);
|
|
1150
|
+
this.filterChanged.emit(next);
|
|
1151
|
+
}
|
|
1152
|
+
onExternalMemberViewRequested() {
|
|
1153
|
+
this.externalMemberViewRequested.emit();
|
|
1154
|
+
}
|
|
1155
|
+
onItemCreated(item) {
|
|
1156
|
+
this.itemCreated.emit(item);
|
|
1157
|
+
}
|
|
1158
|
+
isCategoryExpanded(category) {
|
|
1159
|
+
return this.expandedCategories().has(category);
|
|
1160
|
+
}
|
|
1161
|
+
toggleCategory(category) {
|
|
1162
|
+
this.hasUserToggled.set(true);
|
|
1163
|
+
const next = new Set(this.expandedCategories());
|
|
1164
|
+
if (next.has(category)) {
|
|
1165
|
+
next.delete(category);
|
|
1166
|
+
}
|
|
1167
|
+
else {
|
|
1168
|
+
next.add(category);
|
|
1169
|
+
}
|
|
1170
|
+
this.expandedCategories.set(next);
|
|
1171
|
+
}
|
|
1172
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: PackingListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1173
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: PackingListComponent, isStandalone: true, selector: "c2g-packing-list", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, members: { classPropertyName: "members", publicName: "members", isSignal: true, isRequired: true, transformFunction: null }, currentUserId: { classPropertyName: "currentUserId", publicName: "currentUserId", isSignal: true, isRequired: true, transformFunction: null }, permissions: { classPropertyName: "permissions", publicName: "permissions", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null }, categories: { classPropertyName: "categories", publicName: "categories", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, showPrivateAsSeparateList: { classPropertyName: "showPrivateAsSeparateList", publicName: "showPrivateAsSeparateList", isSignal: true, isRequired: false, transformFunction: null }, showStats: { classPropertyName: "showStats", publicName: "showStats", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemChecked: "itemChecked", itemAssigned: "itemAssigned", itemDeleted: "itemDeleted", itemEditRequested: "itemEditRequested", itemCreated: "itemCreated", personalItemToggled: "personalItemToggled", memberOverlayRequested: "memberOverlayRequested", externalMemberViewRequested: "externalMemberViewRequested", filterChanged: "filterChanged", sortChanged: "sortChanged", rowActionTriggered: "rowActionTriggered" }, ngImport: i0, template: "<section class=\"c2g-packing-list\">\n <header class=\"c2g-packing-list__header\">\n <c2g-packing-list-filters\n [filter]=\"filter()\"\n [members]=\"members()\"\n [labels]=\"labels()\"\n [disabled]=\"permissions().readOnly\"\n (filterChange)=\"onFilterChange($event)\"\n (overlayRequested)=\"onExternalMemberViewRequested()\">\n </c2g-packing-list-filters>\n\n <div class=\"c2g-packing-list__header-actions\">\n <c2g-packing-list-item-create\n [permissions]=\"permissions()\"\n [labels]=\"labels()\"\n [readOnly]=\"permissions().readOnly\"\n [categories]=\"categories()\"\n (itemCreated)=\"onItemCreated($event)\">\n </c2g-packing-list-item-create>\n </div>\n </header>\n\n @if (filteredItems().length === 0) {\n <p class=\"c2g-packing-list__empty\">{{ labels().emptyStateKey }}</p>\n }\n\n <div class=\"c2g-packing-list__categories\">\n @for (group of categoryGroups(); track group.key) {\n <c2g-packing-list-category\n [categoryKey]=\"group.key\"\n [items]=\"group.items\"\n [members]=\"members()\"\n [selectedMemberIds]=\"filter().memberIds\"\n [currentUserId]=\"currentUserId()\"\n [permissions]=\"permissions()\"\n [expanded]=\"isCategoryExpanded(group.key)\"\n (toggle)=\"toggleCategory($event)\"\n (itemChecked)=\"itemChecked.emit($event)\"\n (itemAssigned)=\"itemAssigned.emit($event)\"\n (itemDeleted)=\"itemDeleted.emit($event)\"\n (itemEditRequested)=\"itemEditRequested.emit($event)\"\n (personalItemToggled)=\"personalItemToggled.emit($event)\"\n (memberOverlayRequested)=\"memberOverlayRequested.emit($event)\">\n </c2g-packing-list-category>\n }\n </div>\n\n @if (showPrivateAsSeparateList()) {\n <c2g-packing-list-private-list\n [items]=\"filteredItems()\"\n [members]=\"members()\"\n [selectedMemberIds]=\"filter().memberIds\"\n [currentUserId]=\"currentUserId()\"\n [permissions]=\"permissions()\"\n [labels]=\"labels()\"\n (itemChecked)=\"itemChecked.emit($event)\"\n (itemAssigned)=\"itemAssigned.emit($event)\"\n (itemDeleted)=\"itemDeleted.emit($event)\"\n (itemEditRequested)=\"itemEditRequested.emit($event)\"\n (personalItemToggled)=\"personalItemToggled.emit($event)\"\n (memberOverlayRequested)=\"memberOverlayRequested.emit($event)\">\n </c2g-packing-list-private-list>\n }\n</section>\n", styles: [".c2g-packing-list{display:grid;gap:.9rem}.c2g-packing-list__header{display:flex;justify-content:space-between;align-items:center;gap:.8rem;flex-wrap:wrap}.c2g-packing-list__header-actions{display:flex;align-items:center;gap:.8rem;flex-wrap:wrap}.c2g-packing-list__categories{display:grid;gap:.65rem}.c2g-packing-list__empty{margin:0;border:1px dashed var(--c2g-color-outline);border-radius:.75rem;background:var(--c2g-color-neutral-50);color:var(--c2g-color-text-muted);padding:.85rem;font-size:.9rem}\n"], dependencies: [{ kind: "component", type: PackingListFiltersComponent, selector: "c2g-packing-list-filters", inputs: ["filter", "members", "labels", "disabled"], outputs: ["filterChange", "overlayRequested"] }, { kind: "component", type: PackingListCategoryComponent, selector: "c2g-packing-list-category", inputs: ["categoryKey", "items", "members", "selectedMemberIds", "currentUserId", "permissions", "expanded"], outputs: ["toggle", "itemChecked", "itemAssigned", "itemDeleted", "itemEditRequested", "personalItemToggled", "memberOverlayRequested"] }, { kind: "component", type: PackingListPrivateListComponent, selector: "c2g-packing-list-private-list", inputs: ["items", "members", "selectedMemberIds", "currentUserId", "permissions", "labels"], outputs: ["itemChecked", "itemAssigned", "itemDeleted", "itemEditRequested", "personalItemToggled", "memberOverlayRequested"] }, { kind: "component", type: PackingListItemCreateComponent, selector: "c2g-packing-list-item-create", inputs: ["permissions", "labels", "readOnly", "categories"], outputs: ["itemCreated"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1174
|
+
}
|
|
1175
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: PackingListComponent, decorators: [{
|
|
1176
|
+
type: Component,
|
|
1177
|
+
args: [{ selector: 'c2g-packing-list', standalone: true, imports: [
|
|
1178
|
+
PackingListFiltersComponent,
|
|
1179
|
+
PackingListCategoryComponent,
|
|
1180
|
+
PackingListPrivateListComponent,
|
|
1181
|
+
PackingListItemCreateComponent
|
|
1182
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"c2g-packing-list\">\n <header class=\"c2g-packing-list__header\">\n <c2g-packing-list-filters\n [filter]=\"filter()\"\n [members]=\"members()\"\n [labels]=\"labels()\"\n [disabled]=\"permissions().readOnly\"\n (filterChange)=\"onFilterChange($event)\"\n (overlayRequested)=\"onExternalMemberViewRequested()\">\n </c2g-packing-list-filters>\n\n <div class=\"c2g-packing-list__header-actions\">\n <c2g-packing-list-item-create\n [permissions]=\"permissions()\"\n [labels]=\"labels()\"\n [readOnly]=\"permissions().readOnly\"\n [categories]=\"categories()\"\n (itemCreated)=\"onItemCreated($event)\">\n </c2g-packing-list-item-create>\n </div>\n </header>\n\n @if (filteredItems().length === 0) {\n <p class=\"c2g-packing-list__empty\">{{ labels().emptyStateKey }}</p>\n }\n\n <div class=\"c2g-packing-list__categories\">\n @for (group of categoryGroups(); track group.key) {\n <c2g-packing-list-category\n [categoryKey]=\"group.key\"\n [items]=\"group.items\"\n [members]=\"members()\"\n [selectedMemberIds]=\"filter().memberIds\"\n [currentUserId]=\"currentUserId()\"\n [permissions]=\"permissions()\"\n [expanded]=\"isCategoryExpanded(group.key)\"\n (toggle)=\"toggleCategory($event)\"\n (itemChecked)=\"itemChecked.emit($event)\"\n (itemAssigned)=\"itemAssigned.emit($event)\"\n (itemDeleted)=\"itemDeleted.emit($event)\"\n (itemEditRequested)=\"itemEditRequested.emit($event)\"\n (personalItemToggled)=\"personalItemToggled.emit($event)\"\n (memberOverlayRequested)=\"memberOverlayRequested.emit($event)\">\n </c2g-packing-list-category>\n }\n </div>\n\n @if (showPrivateAsSeparateList()) {\n <c2g-packing-list-private-list\n [items]=\"filteredItems()\"\n [members]=\"members()\"\n [selectedMemberIds]=\"filter().memberIds\"\n [currentUserId]=\"currentUserId()\"\n [permissions]=\"permissions()\"\n [labels]=\"labels()\"\n (itemChecked)=\"itemChecked.emit($event)\"\n (itemAssigned)=\"itemAssigned.emit($event)\"\n (itemDeleted)=\"itemDeleted.emit($event)\"\n (itemEditRequested)=\"itemEditRequested.emit($event)\"\n (personalItemToggled)=\"personalItemToggled.emit($event)\"\n (memberOverlayRequested)=\"memberOverlayRequested.emit($event)\">\n </c2g-packing-list-private-list>\n }\n</section>\n", styles: [".c2g-packing-list{display:grid;gap:.9rem}.c2g-packing-list__header{display:flex;justify-content:space-between;align-items:center;gap:.8rem;flex-wrap:wrap}.c2g-packing-list__header-actions{display:flex;align-items:center;gap:.8rem;flex-wrap:wrap}.c2g-packing-list__categories{display:grid;gap:.65rem}.c2g-packing-list__empty{margin:0;border:1px dashed var(--c2g-color-outline);border-radius:.75rem;background:var(--c2g-color-neutral-50);color:var(--c2g-color-text-muted);padding:.85rem;font-size:.9rem}\n"] }]
|
|
1183
|
+
}], ctorParameters: () => [], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], members: [{ type: i0.Input, args: [{ isSignal: true, alias: "members", required: true }] }], currentUserId: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentUserId", required: true }] }], permissions: [{ type: i0.Input, args: [{ isSignal: true, alias: "permissions", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], categories: [{ type: i0.Input, args: [{ isSignal: true, alias: "categories", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], showPrivateAsSeparateList: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPrivateAsSeparateList", required: false }] }], showStats: [{ type: i0.Input, args: [{ isSignal: true, alias: "showStats", required: false }] }], itemChecked: [{ type: i0.Output, args: ["itemChecked"] }], itemAssigned: [{ type: i0.Output, args: ["itemAssigned"] }], itemDeleted: [{ type: i0.Output, args: ["itemDeleted"] }], itemEditRequested: [{ type: i0.Output, args: ["itemEditRequested"] }], itemCreated: [{ type: i0.Output, args: ["itemCreated"] }], personalItemToggled: [{ type: i0.Output, args: ["personalItemToggled"] }], memberOverlayRequested: [{ type: i0.Output, args: ["memberOverlayRequested"] }], externalMemberViewRequested: [{ type: i0.Output, args: ["externalMemberViewRequested"] }], filterChanged: [{ type: i0.Output, args: ["filterChanged"] }], sortChanged: [{ type: i0.Output, args: ["sortChanged"] }], rowActionTriggered: [{ type: i0.Output, args: ["rowActionTriggered"] }] } });
|
|
1184
|
+
|
|
1185
|
+
class PackingListStatsComponent {
|
|
1186
|
+
items = input.required(...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
1187
|
+
stats = computed(() => {
|
|
1188
|
+
const items = this.items();
|
|
1189
|
+
const packed = items.filter(item => (item.assignments ?? []).some(a => a.status === 'packed' || a.status === 'confirmed')).length;
|
|
1190
|
+
const essential = items.filter(item => !!item.essential).length;
|
|
1191
|
+
return { total: items.length, packed, essential };
|
|
1192
|
+
}, ...(ngDevMode ? [{ debugName: "stats" }] : []));
|
|
1193
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: PackingListStatsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1194
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.19", type: PackingListStatsComponent, isStandalone: true, selector: "c2g-packing-list-stats", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"c2g-pl-stats\">\n <span>{{ stats().packed }}/{{ stats().total }}</span>\n <span>essential: {{ stats().essential }}</span>\n</div>\n", styles: [".c2g-pl-stats{display:inline-flex;gap:.8rem;align-items:center;font-size:.8rem;color:var(--c2g-color-text-secondary);border:1px solid var(--c2g-color-outline);border-radius:999px;padding:.25rem .6rem}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1195
|
+
}
|
|
1196
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: PackingListStatsComponent, decorators: [{
|
|
1197
|
+
type: Component,
|
|
1198
|
+
args: [{ selector: 'c2g-packing-list-stats', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"c2g-pl-stats\">\n <span>{{ stats().packed }}/{{ stats().total }}</span>\n <span>essential: {{ stats().essential }}</span>\n</div>\n", styles: [".c2g-pl-stats{display:inline-flex;gap:.8rem;align-items:center;font-size:.8rem;color:var(--c2g-color-text-secondary);border:1px solid var(--c2g-color-outline);border-radius:999px;padding:.25rem .6rem}\n"] }]
|
|
1199
|
+
}], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }] } });
|
|
1200
|
+
|
|
1201
|
+
const WEATHER_ICON_MAP = {
|
|
1202
|
+
'sunny': '☀️',
|
|
1203
|
+
'clear': '☀️',
|
|
1204
|
+
'partly-cloudy': '⛅',
|
|
1205
|
+
'partly_cloudy': '⛅',
|
|
1206
|
+
'cloudy': '☁️',
|
|
1207
|
+
'overcast': '☁️',
|
|
1208
|
+
'light-rain': '🌦️',
|
|
1209
|
+
'light_rain': '🌦️',
|
|
1210
|
+
'rain': '🌧️',
|
|
1211
|
+
'heavy-rain': '⛈️',
|
|
1212
|
+
'heavy_rain': '⛈️',
|
|
1213
|
+
'snow': '❄️',
|
|
1214
|
+
'fog': '🌫️',
|
|
1215
|
+
'thunder': '⛈️',
|
|
1216
|
+
'wind': '💨',
|
|
1217
|
+
};
|
|
1218
|
+
/**
|
|
1219
|
+
* Resolves a backend weather icon string to an emoji.
|
|
1220
|
+
* @param icon Raw icon string from the API (e.g. "partly-cloudy")
|
|
1221
|
+
* @param fallback Emoji to return for unrecognised keys. Defaults to '🌤️'.
|
|
1222
|
+
* Pass `icon` as fallback to preserve the raw string (useful
|
|
1223
|
+
* for debugging in the full weather widget).
|
|
1224
|
+
*/
|
|
1225
|
+
function resolveWeatherIcon(icon, fallback = '🌤️') {
|
|
1226
|
+
if (!icon)
|
|
1227
|
+
return fallback;
|
|
1228
|
+
return WEATHER_ICON_MAP[icon.toLowerCase()] ?? fallback;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
class WeatherWidgetComponent {
|
|
1232
|
+
currentDay = input(null, ...(ngDevMode ? [{ debugName: "currentDay" }] : []));
|
|
1233
|
+
forecastSlots = input([], ...(ngDevMode ? [{ debugName: "forecastSlots" }] : []));
|
|
1234
|
+
destination = input('', ...(ngDevMode ? [{ debugName: "destination" }] : []));
|
|
1235
|
+
dates = input(null, ...(ngDevMode ? [{ debugName: "dates" }] : []));
|
|
1236
|
+
getWeatherIcon(icon) {
|
|
1237
|
+
return resolveWeatherIcon(icon, icon);
|
|
1238
|
+
}
|
|
1239
|
+
formatDate(date) {
|
|
1240
|
+
return new Date(date).toLocaleDateString('de-DE', {
|
|
1241
|
+
weekday: 'short',
|
|
1242
|
+
day: 'numeric',
|
|
1243
|
+
month: 'short'
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
formatShortDate(date) {
|
|
1247
|
+
return new Date(date).toLocaleDateString('de-DE', { day: 'numeric', month: 'short' });
|
|
1248
|
+
}
|
|
1249
|
+
getSlotLabel(index, total) {
|
|
1250
|
+
if (total === 1)
|
|
1251
|
+
return 'Reise';
|
|
1252
|
+
if (index === 0)
|
|
1253
|
+
return 'Anreise';
|
|
1254
|
+
if (index === total - 1)
|
|
1255
|
+
return 'Abreise';
|
|
1256
|
+
return 'Mitte';
|
|
1257
|
+
}
|
|
1258
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: WeatherWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1259
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: WeatherWidgetComponent, isStandalone: true, selector: "c2g-weather-widget", inputs: { currentDay: { classPropertyName: "currentDay", publicName: "currentDay", isSignal: true, isRequired: false, transformFunction: null }, forecastSlots: { classPropertyName: "forecastSlots", publicName: "forecastSlots", isSignal: true, isRequired: false, transformFunction: null }, destination: { classPropertyName: "destination", publicName: "destination", isSignal: true, isRequired: false, transformFunction: null }, dates: { classPropertyName: "dates", publicName: "dates", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (currentDay()) {\n <div class=\"wx-widget\">\n <!-- Hero: current/start-day weather -->\n <div class=\"wx-hero\">\n <span class=\"wx-hero__icon\">{{ getWeatherIcon(currentDay()!.icon) }}</span>\n <div class=\"wx-hero__body\">\n <div class=\"wx-hero__temp\">{{ currentDay()!.temperature.max }}<sup>\u00B0</sup></div>\n <div class=\"wx-hero__condition\">{{ currentDay()!.description }}</div>\n @if (destination()) {\n <div class=\"wx-hero__meta\">\n <span class=\"wx-hero__location\">\uD83D\uDCCD {{ destination() }}</span>\n @if (dates()) {\n <span class=\"wx-hero__daterange\">\n {{ formatShortDate(dates()!.from) }} \u2013 {{ formatShortDate(dates()!.to) }}\n </span>\n }\n </div>\n }\n </div>\n <div class=\"wx-hero__range\">\n <span class=\"wx-range__high\">\u2191 {{ currentDay()!.temperature.max }}\u00B0</span>\n <span class=\"wx-range__low\">\u2193 {{ currentDay()!.temperature.min }}\u00B0</span>\n </div>\n </div>\n\n <!-- Forecast slots -->\n @if (forecastSlots().length > 1) {\n <div class=\"wx-forecast\" [attr.data-slots]=\"forecastSlots().length\">\n @for (slot of forecastSlots(); track $index) {\n <div class=\"wx-slot\">\n <span class=\"wx-slot__label\">{{ getSlotLabel($index, forecastSlots().length) }}</span>\n <span class=\"wx-slot__date\">{{ formatDate(slot.date) }}</span>\n <span class=\"wx-slot__icon\">{{ getWeatherIcon(slot.icon) }}</span>\n <div class=\"wx-slot__temps\">\n <span class=\"wx-slot__max\">{{ slot.temperature.max }}\u00B0</span>\n <span class=\"wx-slot__min\">{{ slot.temperature.min }}\u00B0</span>\n </div>\n </div>\n }\n </div>\n }\n </div>\n} @else {\n <div class=\"wx-empty\">Wetter erscheint nach Ziel- und Datumsauswahl.</div>\n}\n", styles: [".wx-widget{display:flex;flex-direction:column;gap:.75rem;font-family:inherit}.wx-hero{display:flex;align-items:center;gap:.85rem;padding:.85rem 1rem;border-radius:14px;background:linear-gradient(135deg,var(--c2g-color-primary-light) 0%,var(--c2g-color-primary-container) 100%);border:1px solid var(--c2g-color-border-subtle)}.wx-hero__icon{font-size:2.6rem;line-height:1;flex-shrink:0}.wx-hero__body{flex:1;min-width:0}.wx-hero__temp{font-size:2rem;font-weight:700;color:var(--c2g-color-text-primary);line-height:1;letter-spacing:-.03em}.wx-hero__temp sup{font-size:1rem;font-weight:500;vertical-align:super}.wx-hero__condition{font-size:.82rem;color:var(--c2g-color-text-secondary);margin-top:.15rem;font-weight:500}.wx-hero__meta{display:flex;flex-wrap:wrap;gap:.3rem .5rem;margin-top:.3rem;font-size:.75rem;color:var(--c2g-color-text-muted)}.wx-hero__location{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:22ch}.wx-hero__daterange{opacity:.75;white-space:nowrap}.wx-range__high{display:block;font-size:.82rem;font-weight:700;color:var(--c2g-color-primary-dark)}.wx-range__low{display:block;font-size:.78rem;color:var(--c2g-color-info);margin-top:.1rem}.wx-forecast{display:grid;grid-template-columns:repeat(2,1fr);gap:.5rem}.wx-forecast[data-slots=\"3\"]{grid-template-columns:repeat(3,1fr)}.wx-slot{display:flex;flex-direction:column;align-items:center;gap:.18rem;padding:.6rem .4rem .55rem;border-radius:10px;border:1px solid var(--c2g-color-border-soft);background:var(--c2g-color-surface);text-align:center}.wx-slot__label{font-size:.62rem;font-weight:700;color:var(--c2g-color-primary-dark);text-transform:uppercase;letter-spacing:.06em}.wx-slot__date{font-size:.67rem;color:var(--c2g-color-text-muted);line-height:1.2}.wx-slot__icon{font-size:1.5rem;margin:.15rem 0;line-height:1}.wx-slot__temps{display:flex;gap:.3rem;align-items:baseline}.wx-slot__max{font-size:.88rem;font-weight:700;color:var(--c2g-color-text-primary)}.wx-slot__min{font-size:.75rem;color:var(--c2g-color-info)}.wx-empty{padding:.75rem;font-size:.85rem;color:var(--c2g-color-text-muted);text-align:center}@media(max-width:480px){.wx-hero__icon{font-size:2.1rem}.wx-hero__temp{font-size:1.7rem}.wx-slot{padding:.5rem .25rem}.wx-slot__icon{font-size:1.25rem}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1260
|
+
}
|
|
1261
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: WeatherWidgetComponent, decorators: [{
|
|
1262
|
+
type: Component,
|
|
1263
|
+
args: [{ selector: 'c2g-weather-widget', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (currentDay()) {\n <div class=\"wx-widget\">\n <!-- Hero: current/start-day weather -->\n <div class=\"wx-hero\">\n <span class=\"wx-hero__icon\">{{ getWeatherIcon(currentDay()!.icon) }}</span>\n <div class=\"wx-hero__body\">\n <div class=\"wx-hero__temp\">{{ currentDay()!.temperature.max }}<sup>\u00B0</sup></div>\n <div class=\"wx-hero__condition\">{{ currentDay()!.description }}</div>\n @if (destination()) {\n <div class=\"wx-hero__meta\">\n <span class=\"wx-hero__location\">\uD83D\uDCCD {{ destination() }}</span>\n @if (dates()) {\n <span class=\"wx-hero__daterange\">\n {{ formatShortDate(dates()!.from) }} \u2013 {{ formatShortDate(dates()!.to) }}\n </span>\n }\n </div>\n }\n </div>\n <div class=\"wx-hero__range\">\n <span class=\"wx-range__high\">\u2191 {{ currentDay()!.temperature.max }}\u00B0</span>\n <span class=\"wx-range__low\">\u2193 {{ currentDay()!.temperature.min }}\u00B0</span>\n </div>\n </div>\n\n <!-- Forecast slots -->\n @if (forecastSlots().length > 1) {\n <div class=\"wx-forecast\" [attr.data-slots]=\"forecastSlots().length\">\n @for (slot of forecastSlots(); track $index) {\n <div class=\"wx-slot\">\n <span class=\"wx-slot__label\">{{ getSlotLabel($index, forecastSlots().length) }}</span>\n <span class=\"wx-slot__date\">{{ formatDate(slot.date) }}</span>\n <span class=\"wx-slot__icon\">{{ getWeatherIcon(slot.icon) }}</span>\n <div class=\"wx-slot__temps\">\n <span class=\"wx-slot__max\">{{ slot.temperature.max }}\u00B0</span>\n <span class=\"wx-slot__min\">{{ slot.temperature.min }}\u00B0</span>\n </div>\n </div>\n }\n </div>\n }\n </div>\n} @else {\n <div class=\"wx-empty\">Wetter erscheint nach Ziel- und Datumsauswahl.</div>\n}\n", styles: [".wx-widget{display:flex;flex-direction:column;gap:.75rem;font-family:inherit}.wx-hero{display:flex;align-items:center;gap:.85rem;padding:.85rem 1rem;border-radius:14px;background:linear-gradient(135deg,var(--c2g-color-primary-light) 0%,var(--c2g-color-primary-container) 100%);border:1px solid var(--c2g-color-border-subtle)}.wx-hero__icon{font-size:2.6rem;line-height:1;flex-shrink:0}.wx-hero__body{flex:1;min-width:0}.wx-hero__temp{font-size:2rem;font-weight:700;color:var(--c2g-color-text-primary);line-height:1;letter-spacing:-.03em}.wx-hero__temp sup{font-size:1rem;font-weight:500;vertical-align:super}.wx-hero__condition{font-size:.82rem;color:var(--c2g-color-text-secondary);margin-top:.15rem;font-weight:500}.wx-hero__meta{display:flex;flex-wrap:wrap;gap:.3rem .5rem;margin-top:.3rem;font-size:.75rem;color:var(--c2g-color-text-muted)}.wx-hero__location{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:22ch}.wx-hero__daterange{opacity:.75;white-space:nowrap}.wx-range__high{display:block;font-size:.82rem;font-weight:700;color:var(--c2g-color-primary-dark)}.wx-range__low{display:block;font-size:.78rem;color:var(--c2g-color-info);margin-top:.1rem}.wx-forecast{display:grid;grid-template-columns:repeat(2,1fr);gap:.5rem}.wx-forecast[data-slots=\"3\"]{grid-template-columns:repeat(3,1fr)}.wx-slot{display:flex;flex-direction:column;align-items:center;gap:.18rem;padding:.6rem .4rem .55rem;border-radius:10px;border:1px solid var(--c2g-color-border-soft);background:var(--c2g-color-surface);text-align:center}.wx-slot__label{font-size:.62rem;font-weight:700;color:var(--c2g-color-primary-dark);text-transform:uppercase;letter-spacing:.06em}.wx-slot__date{font-size:.67rem;color:var(--c2g-color-text-muted);line-height:1.2}.wx-slot__icon{font-size:1.5rem;margin:.15rem 0;line-height:1}.wx-slot__temps{display:flex;gap:.3rem;align-items:baseline}.wx-slot__max{font-size:.88rem;font-weight:700;color:var(--c2g-color-text-primary)}.wx-slot__min{font-size:.75rem;color:var(--c2g-color-info)}.wx-empty{padding:.75rem;font-size:.85rem;color:var(--c2g-color-text-muted);text-align:center}@media(max-width:480px){.wx-hero__icon{font-size:2.1rem}.wx-hero__temp{font-size:1.7rem}.wx-slot{padding:.5rem .25rem}.wx-slot__icon{font-size:1.25rem}}\n"] }]
|
|
1264
|
+
}], propDecorators: { currentDay: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentDay", required: false }] }], forecastSlots: [{ type: i0.Input, args: [{ isSignal: true, alias: "forecastSlots", required: false }] }], destination: [{ type: i0.Input, args: [{ isSignal: true, alias: "destination", required: false }] }], dates: [{ type: i0.Input, args: [{ isSignal: true, alias: "dates", required: false }] }] } });
|
|
1265
|
+
|
|
1266
|
+
class MemberTagsComponent {
|
|
1267
|
+
isCurrentUser = input(false, ...(ngDevMode ? [{ debugName: "isCurrentUser" }] : []));
|
|
1268
|
+
isRegistered = input(true, ...(ngDevMode ? [{ debugName: "isRegistered" }] : []));
|
|
1269
|
+
roleKey = input(null, ...(ngDevMode ? [{ debugName: "roleKey" }] : []));
|
|
1270
|
+
getRoleLabel(roleKey) {
|
|
1271
|
+
switch (roleKey) {
|
|
1272
|
+
case 'organizer':
|
|
1273
|
+
return '🧭 Organisator';
|
|
1274
|
+
case 'treasurer':
|
|
1275
|
+
return '💰 Kassenwart';
|
|
1276
|
+
case 'transport':
|
|
1277
|
+
return '🚙 Fahrkoordination';
|
|
1278
|
+
case 'cook':
|
|
1279
|
+
return '👨🍳 Kochen';
|
|
1280
|
+
case 'firstaid':
|
|
1281
|
+
return '🩺 Erste Hilfe';
|
|
1282
|
+
case 'equipment':
|
|
1283
|
+
return '🎒 Ausrüstung';
|
|
1284
|
+
default:
|
|
1285
|
+
return '';
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MemberTagsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1289
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: MemberTagsComponent, isStandalone: true, selector: "c2g-member-tags", inputs: { isCurrentUser: { classPropertyName: "isCurrentUser", publicName: "isCurrentUser", isSignal: true, isRequired: false, transformFunction: null }, isRegistered: { classPropertyName: "isRegistered", publicName: "isRegistered", isSignal: true, isRequired: false, transformFunction: null }, roleKey: { classPropertyName: "roleKey", publicName: "roleKey", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (isCurrentUser()) {\n <span class=\"tw-member__you-badge\">Du</span>\n}\n\n@if (!isRegistered()) {\n <span class=\"tw-member__unreg-badge\">Ohne Account</span>\n}\n\n@if (roleKey()) {\n <span class=\"tw-member__role\">{{ getRoleLabel(roleKey()) }}</span>\n}\n", styles: [".tw-member__you-badge{font-size:.62rem;font-weight:700;padding:.1rem .45rem;border-radius:999px;background:var(--c2g-color-secondary-dark);color:var(--c2g-color-surface);text-transform:uppercase;letter-spacing:.06em}.tw-member__unreg-badge{font-size:.62rem;font-weight:700;padding:.1rem .45rem;border-radius:999px;background:var(--c2g-color-primary-container);color:var(--c2g-color-on-primary-container);text-transform:uppercase;letter-spacing:.06em}.tw-member__role{font-size:.78rem;font-weight:600;color:var(--c2g-color-secondary-dark);padding:.25rem .65rem;border-radius:999px;background:var(--c2g-color-secondary-container);white-space:nowrap}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1290
|
+
}
|
|
1291
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MemberTagsComponent, decorators: [{
|
|
1292
|
+
type: Component,
|
|
1293
|
+
args: [{ selector: 'c2g-member-tags', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (isCurrentUser()) {\n <span class=\"tw-member__you-badge\">Du</span>\n}\n\n@if (!isRegistered()) {\n <span class=\"tw-member__unreg-badge\">Ohne Account</span>\n}\n\n@if (roleKey()) {\n <span class=\"tw-member__role\">{{ getRoleLabel(roleKey()) }}</span>\n}\n", styles: [".tw-member__you-badge{font-size:.62rem;font-weight:700;padding:.1rem .45rem;border-radius:999px;background:var(--c2g-color-secondary-dark);color:var(--c2g-color-surface);text-transform:uppercase;letter-spacing:.06em}.tw-member__unreg-badge{font-size:.62rem;font-weight:700;padding:.1rem .45rem;border-radius:999px;background:var(--c2g-color-primary-container);color:var(--c2g-color-on-primary-container);text-transform:uppercase;letter-spacing:.06em}.tw-member__role{font-size:.78rem;font-weight:600;color:var(--c2g-color-secondary-dark);padding:.25rem .65rem;border-radius:999px;background:var(--c2g-color-secondary-container);white-space:nowrap}\n"] }]
|
|
1294
|
+
}], propDecorators: { isCurrentUser: [{ type: i0.Input, args: [{ isSignal: true, alias: "isCurrentUser", required: false }] }], isRegistered: [{ type: i0.Input, args: [{ isSignal: true, alias: "isRegistered", required: false }] }], roleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "roleKey", required: false }] }] } });
|
|
1295
|
+
|
|
1296
|
+
class ActionMenuComponent {
|
|
1297
|
+
items = input.required(...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
1298
|
+
triggerLabel = input('...', ...(ngDevMode ? [{ debugName: "triggerLabel" }] : []));
|
|
1299
|
+
ariaLabel = input('Aktionen', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
|
|
1300
|
+
itemSelected = output();
|
|
1301
|
+
select(item) {
|
|
1302
|
+
if (item.disabled)
|
|
1303
|
+
return;
|
|
1304
|
+
this.itemSelected.emit(item);
|
|
1305
|
+
}
|
|
1306
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ActionMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1307
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: ActionMenuComponent, isStandalone: true, selector: "c2g-action-menu", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, triggerLabel: { classPropertyName: "triggerLabel", publicName: "triggerLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemSelected: "itemSelected" }, ngImport: i0, template: "<div class=\"c2g-action-menu\">\n <button\n class=\"c2g-action-menu__trigger\"\n type=\"button\"\n [attr.aria-label]=\"ariaLabel()\"\n [cdkMenuTriggerFor]=\"menuPanel\">\n {{ triggerLabel() }}\n </button>\n\n <ng-template #menuPanel>\n <ul class=\"c2g-action-menu__list\" cdkMenu>\n @for (item of items(); track item.key) {\n <li role=\"none\">\n <button\n class=\"c2g-action-menu__item\"\n [class.c2g-action-menu__item--danger]=\"item.variant === 'danger'\"\n [class.c2g-action-menu__item--disabled]=\"item.disabled\"\n [disabled]=\"item.disabled ?? false\"\n cdkMenuItem\n type=\"button\"\n (cdkMenuItemTriggered)=\"select(item)\">\n @if (item.icon) {\n <span class=\"c2g-action-menu__item-icon\" aria-hidden=\"true\">{{ item.icon }}</span>\n }\n <span>{{ item.label }}</span>\n </button>\n </li>\n }\n </ul>\n </ng-template>\n</div>\n", styles: [".c2g-action-menu{position:relative;display:inline-flex}.c2g-action-menu__trigger{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:8px;border:1px solid transparent;background:transparent;color:var(--c2g-color-text-muted);font-size:1.1rem;font-weight:700;letter-spacing:.05em;cursor:pointer;transition:background .15s,border-color .15s,color .15s}.c2g-action-menu__trigger:hover{background:var(--c2g-color-neutral-100);border-color:var(--c2g-color-outline);color:var(--c2g-color-text-primary)}.c2g-action-menu__trigger[aria-expanded=true]{background:var(--c2g-color-secondary-container);border-color:var(--c2g-color-outline-variant);color:var(--c2g-color-secondary-dark)}.c2g-action-menu__list{position:absolute;top:calc(100% + 4px);right:0;z-index:200;min-width:160px;margin:0;padding:.3rem;list-style:none;background:var(--c2g-color-surface);border:1px solid var(--c2g-color-outline-variant);border-radius:10px;box-shadow:0 4px 16px #0000001a,0 1px 4px #0000000f}.c2g-action-menu__item{display:flex;align-items:center;gap:.5rem;width:100%;padding:.5rem .75rem;border:none;border-radius:7px;background:transparent;font-size:.85rem;font-weight:500;color:var(--c2g-color-text-primary);cursor:pointer;text-align:left;transition:background .1s}.c2g-action-menu__item:hover:not([disabled]){background:var(--c2g-color-neutral-100)}.c2g-action-menu__item--danger{color:var(--c2g-color-error)}.c2g-action-menu__item--danger:hover:not([disabled]){background:var(--c2g-color-primary-light)}.c2g-action-menu__item--disabled,.c2g-action-menu__item[disabled]{opacity:.4;cursor:not-allowed}.c2g-action-menu__item-icon{font-size:1rem;line-height:1;flex-shrink:0}\n"], dependencies: [{ kind: "directive", type: CdkMenu, selector: "[cdkMenu]", outputs: ["closed"], exportAs: ["cdkMenu"] }, { kind: "directive", type: CdkMenuItem, selector: "[cdkMenuItem]", inputs: ["cdkMenuItemDisabled", "cdkMenuitemTypeaheadLabel"], outputs: ["cdkMenuItemTriggered"], exportAs: ["cdkMenuItem"] }, { kind: "directive", type: CdkMenuTrigger, selector: "[cdkMenuTriggerFor]", inputs: ["cdkMenuTriggerFor", "cdkMenuPosition", "cdkMenuTriggerData"], outputs: ["cdkMenuOpened", "cdkMenuClosed"], exportAs: ["cdkMenuTriggerFor"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1308
|
+
}
|
|
1309
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ActionMenuComponent, decorators: [{
|
|
1310
|
+
type: Component,
|
|
1311
|
+
args: [{ selector: 'c2g-action-menu', standalone: true, imports: [CdkMenu, CdkMenuItem, CdkMenuTrigger], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"c2g-action-menu\">\n <button\n class=\"c2g-action-menu__trigger\"\n type=\"button\"\n [attr.aria-label]=\"ariaLabel()\"\n [cdkMenuTriggerFor]=\"menuPanel\">\n {{ triggerLabel() }}\n </button>\n\n <ng-template #menuPanel>\n <ul class=\"c2g-action-menu__list\" cdkMenu>\n @for (item of items(); track item.key) {\n <li role=\"none\">\n <button\n class=\"c2g-action-menu__item\"\n [class.c2g-action-menu__item--danger]=\"item.variant === 'danger'\"\n [class.c2g-action-menu__item--disabled]=\"item.disabled\"\n [disabled]=\"item.disabled ?? false\"\n cdkMenuItem\n type=\"button\"\n (cdkMenuItemTriggered)=\"select(item)\">\n @if (item.icon) {\n <span class=\"c2g-action-menu__item-icon\" aria-hidden=\"true\">{{ item.icon }}</span>\n }\n <span>{{ item.label }}</span>\n </button>\n </li>\n }\n </ul>\n </ng-template>\n</div>\n", styles: [".c2g-action-menu{position:relative;display:inline-flex}.c2g-action-menu__trigger{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:8px;border:1px solid transparent;background:transparent;color:var(--c2g-color-text-muted);font-size:1.1rem;font-weight:700;letter-spacing:.05em;cursor:pointer;transition:background .15s,border-color .15s,color .15s}.c2g-action-menu__trigger:hover{background:var(--c2g-color-neutral-100);border-color:var(--c2g-color-outline);color:var(--c2g-color-text-primary)}.c2g-action-menu__trigger[aria-expanded=true]{background:var(--c2g-color-secondary-container);border-color:var(--c2g-color-outline-variant);color:var(--c2g-color-secondary-dark)}.c2g-action-menu__list{position:absolute;top:calc(100% + 4px);right:0;z-index:200;min-width:160px;margin:0;padding:.3rem;list-style:none;background:var(--c2g-color-surface);border:1px solid var(--c2g-color-outline-variant);border-radius:10px;box-shadow:0 4px 16px #0000001a,0 1px 4px #0000000f}.c2g-action-menu__item{display:flex;align-items:center;gap:.5rem;width:100%;padding:.5rem .75rem;border:none;border-radius:7px;background:transparent;font-size:.85rem;font-weight:500;color:var(--c2g-color-text-primary);cursor:pointer;text-align:left;transition:background .1s}.c2g-action-menu__item:hover:not([disabled]){background:var(--c2g-color-neutral-100)}.c2g-action-menu__item--danger{color:var(--c2g-color-error)}.c2g-action-menu__item--danger:hover:not([disabled]){background:var(--c2g-color-primary-light)}.c2g-action-menu__item--disabled,.c2g-action-menu__item[disabled]{opacity:.4;cursor:not-allowed}.c2g-action-menu__item-icon{font-size:1rem;line-height:1;flex-shrink:0}\n"] }]
|
|
1312
|
+
}], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], triggerLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "triggerLabel", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], itemSelected: [{ type: i0.Output, args: ["itemSelected"] }] } });
|
|
1313
|
+
|
|
1314
|
+
class MemberItemComponent {
|
|
1315
|
+
member = input.required(...(ngDevMode ? [{ debugName: "member" }] : []));
|
|
1316
|
+
remove = output();
|
|
1317
|
+
invite = output();
|
|
1318
|
+
edit = output();
|
|
1319
|
+
avatarBackgroundColor = computed(() => this.member().isCurrentUser ? '#1a6044' : '#e8f0ea', ...(ngDevMode ? [{ debugName: "avatarBackgroundColor" }] : []));
|
|
1320
|
+
avatarTextColor = computed(() => this.member().isCurrentUser ? '#ffffff' : '#1a6044', ...(ngDevMode ? [{ debugName: "avatarTextColor" }] : []));
|
|
1321
|
+
menuItems = computed(() => {
|
|
1322
|
+
const m = this.member();
|
|
1323
|
+
const items = [];
|
|
1324
|
+
if (m.isRegistered !== false) {
|
|
1325
|
+
items.push({ key: 'invite', label: 'Einladen', icon: '✉️' });
|
|
1326
|
+
}
|
|
1327
|
+
else {
|
|
1328
|
+
items.push({ key: 'edit', label: 'Bearbeiten', icon: '✏️' });
|
|
1329
|
+
}
|
|
1330
|
+
items.push({ key: 'remove', label: 'Entfernen', icon: '🗑️', variant: 'danger' });
|
|
1331
|
+
return items;
|
|
1332
|
+
}, ...(ngDevMode ? [{ debugName: "menuItems" }] : []));
|
|
1333
|
+
onMenuItemSelected(item) {
|
|
1334
|
+
const m = this.member();
|
|
1335
|
+
if (item.key === 'remove')
|
|
1336
|
+
this.remove.emit(m);
|
|
1337
|
+
else if (item.key === 'invite')
|
|
1338
|
+
this.invite.emit(m);
|
|
1339
|
+
else if (item.key === 'edit')
|
|
1340
|
+
this.edit.emit(m);
|
|
1341
|
+
}
|
|
1342
|
+
getMemberTypeLabel(type) {
|
|
1343
|
+
switch (type) {
|
|
1344
|
+
case 'adult':
|
|
1345
|
+
return 'Erwachsener';
|
|
1346
|
+
case 'child':
|
|
1347
|
+
return 'Kind';
|
|
1348
|
+
case 'pet':
|
|
1349
|
+
return 'Haustier';
|
|
1350
|
+
default:
|
|
1351
|
+
return type;
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MemberItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1355
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.19", type: MemberItemComponent, isStandalone: true, selector: "c2g-member-item", inputs: { member: { classPropertyName: "member", publicName: "member", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { remove: "remove", invite: "invite", edit: "edit" }, ngImport: i0, template: "<c2g-avatar\n [initials]=\"member().avatarInitials\"\n [name]=\"member().name\"\n [backgroundColor]=\"avatarBackgroundColor()\"\n [textColor]=\"avatarTextColor()\"\n ringTone=\"neutral\">\n</c2g-avatar>\n\n<div class=\"tw-member__info\">\n <span class=\"tw-member__name\">\n {{ member().name }}\n <c2g-member-tags\n [isCurrentUser]=\"member().isCurrentUser ?? false\"\n [isRegistered]=\"member().isRegistered ?? true\">\n </c2g-member-tags>\n </span>\n <span class=\"tw-member__type\">{{ getMemberTypeLabel(member().type) }}</span>\n</div>\n\n<c2g-member-tags\n [isCurrentUser]=\"false\"\n [isRegistered]=\"true\"\n [roleKey]=\"member().roleKey\">\n</c2g-member-tags>\n\n<c2g-action-menu\n [items]=\"menuItems()\"\n ariaLabel=\"Mitglied-Aktionen\"\n (itemSelected)=\"onMenuItemSelected($event)\">\n</c2g-action-menu>\n", styles: [":host{display:contents}.tw-member__info{display:flex;flex-direction:column;flex:1;min-width:0}.tw-member__name{font-size:.9rem;font-weight:700;color:var(--c2g-color-text-primary);display:flex;align-items:center;gap:.4rem}.tw-member__type{font-size:.76rem;color:var(--c2g-color-text-muted)}\n"], dependencies: [{ kind: "component", type: MemberTagsComponent, selector: "c2g-member-tags", inputs: ["isCurrentUser", "isRegistered", "roleKey"] }, { kind: "component", type: ActionMenuComponent, selector: "c2g-action-menu", inputs: ["items", "triggerLabel", "ariaLabel"], outputs: ["itemSelected"] }, { kind: "component", type: AvatarComponent, selector: "c2g-avatar", inputs: ["name", "initials", "imageUrl", "ariaLabel", "size", "backgroundColor", "textColor", "ringTone", "ringColor", "clickable", "disabled", "badge"], outputs: ["avatarClick"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1356
|
+
}
|
|
1357
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MemberItemComponent, decorators: [{
|
|
1358
|
+
type: Component,
|
|
1359
|
+
args: [{ selector: 'c2g-member-item', standalone: true, imports: [MemberTagsComponent, ActionMenuComponent, AvatarComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<c2g-avatar\n [initials]=\"member().avatarInitials\"\n [name]=\"member().name\"\n [backgroundColor]=\"avatarBackgroundColor()\"\n [textColor]=\"avatarTextColor()\"\n ringTone=\"neutral\">\n</c2g-avatar>\n\n<div class=\"tw-member__info\">\n <span class=\"tw-member__name\">\n {{ member().name }}\n <c2g-member-tags\n [isCurrentUser]=\"member().isCurrentUser ?? false\"\n [isRegistered]=\"member().isRegistered ?? true\">\n </c2g-member-tags>\n </span>\n <span class=\"tw-member__type\">{{ getMemberTypeLabel(member().type) }}</span>\n</div>\n\n<c2g-member-tags\n [isCurrentUser]=\"false\"\n [isRegistered]=\"true\"\n [roleKey]=\"member().roleKey\">\n</c2g-member-tags>\n\n<c2g-action-menu\n [items]=\"menuItems()\"\n ariaLabel=\"Mitglied-Aktionen\"\n (itemSelected)=\"onMenuItemSelected($event)\">\n</c2g-action-menu>\n", styles: [":host{display:contents}.tw-member__info{display:flex;flex-direction:column;flex:1;min-width:0}.tw-member__name{font-size:.9rem;font-weight:700;color:var(--c2g-color-text-primary);display:flex;align-items:center;gap:.4rem}.tw-member__type{font-size:.76rem;color:var(--c2g-color-text-muted)}\n"] }]
|
|
1360
|
+
}], propDecorators: { member: [{ type: i0.Input, args: [{ isSignal: true, alias: "member", required: true }] }], remove: [{ type: i0.Output, args: ["remove"] }], invite: [{ type: i0.Output, args: ["invite"] }], edit: [{ type: i0.Output, args: ["edit"] }] } });
|
|
1361
|
+
|
|
1362
|
+
class MemberListComponent {
|
|
1363
|
+
members = input.required(...(ngDevMode ? [{ debugName: "members" }] : []));
|
|
1364
|
+
memberRemove = output();
|
|
1365
|
+
memberInvite = output();
|
|
1366
|
+
memberEdit = output();
|
|
1367
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MemberListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1368
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: MemberListComponent, isStandalone: true, selector: "c2g-member-list", inputs: { members: { classPropertyName: "members", publicName: "members", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { memberRemove: "memberRemove", memberInvite: "memberInvite", memberEdit: "memberEdit" }, ngImport: i0, template: "<ul class=\"tw-member-list\">\n @for (member of members(); track member.id) {\n <li class=\"tw-member\">\n <c2g-member-item\n [member]=\"member\"\n (remove)=\"memberRemove.emit($event)\"\n (invite)=\"memberInvite.emit($event)\"\n (edit)=\"memberEdit.emit($event)\">\n </c2g-member-item>\n </li>\n }\n</ul>\n", styles: [".tw-member-list{list-style:none;margin:0;padding:0}.tw-member{display:flex;align-items:center;gap:1rem;padding:.9rem 1.4rem;border-bottom:1px solid var(--c2g-color-neutral-100)}.tw-member:last-child{border-bottom:none}\n"], dependencies: [{ kind: "component", type: MemberItemComponent, selector: "c2g-member-item", inputs: ["member"], outputs: ["remove", "invite", "edit"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1369
|
+
}
|
|
1370
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MemberListComponent, decorators: [{
|
|
1371
|
+
type: Component,
|
|
1372
|
+
args: [{ selector: 'c2g-member-list', standalone: true, imports: [MemberItemComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ul class=\"tw-member-list\">\n @for (member of members(); track member.id) {\n <li class=\"tw-member\">\n <c2g-member-item\n [member]=\"member\"\n (remove)=\"memberRemove.emit($event)\"\n (invite)=\"memberInvite.emit($event)\"\n (edit)=\"memberEdit.emit($event)\">\n </c2g-member-item>\n </li>\n }\n</ul>\n", styles: [".tw-member-list{list-style:none;margin:0;padding:0}.tw-member{display:flex;align-items:center;gap:1rem;padding:.9rem 1.4rem;border-bottom:1px solid var(--c2g-color-neutral-100)}.tw-member:last-child{border-bottom:none}\n"] }]
|
|
1373
|
+
}], propDecorators: { members: [{ type: i0.Input, args: [{ isSignal: true, alias: "members", required: true }] }], memberRemove: [{ type: i0.Output, args: ["memberRemove"] }], memberInvite: [{ type: i0.Output, args: ["memberInvite"] }], memberEdit: [{ type: i0.Output, args: ["memberEdit"] }] } });
|
|
1374
|
+
|
|
1375
|
+
class MemberPanelComponent {
|
|
1376
|
+
members = input.required(...(ngDevMode ? [{ debugName: "members" }] : []));
|
|
1377
|
+
memberRemove = output();
|
|
1378
|
+
memberInvite = output();
|
|
1379
|
+
memberEdit = output();
|
|
1380
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MemberPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1381
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.19", type: MemberPanelComponent, isStandalone: true, selector: "c2g-member-panel", inputs: { members: { classPropertyName: "members", publicName: "members", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { memberRemove: "memberRemove", memberInvite: "memberInvite", memberEdit: "memberEdit" }, ngImport: i0, template: "<div class=\"tw-card\">\n <div class=\"tw-card__header\">\n <h2 class=\"tw-card__title\">Teilnehmer ({{ members().length }})</h2>\n <ng-content select=\"[slot=header-actions]\"></ng-content>\n </div>\n\n <c2g-member-list\n [members]=\"members()\"\n (memberRemove)=\"memberRemove.emit($event)\"\n (memberInvite)=\"memberInvite.emit($event)\"\n (memberEdit)=\"memberEdit.emit($event)\">\n </c2g-member-list>\n</div>\n", styles: [".tw-card{background:var(--c2g-color-surface);border-radius:12px;border:1px solid var(--c2g-color-outline-variant);overflow:hidden}.tw-card__header{display:flex;align-items:center;justify-content:space-between;padding:1.2rem 1.4rem;border-bottom:1px solid var(--c2g-color-neutral-100)}.tw-card__title{margin:0;font-size:.96rem;font-weight:700;color:var(--c2g-color-text-primary)}\n"], dependencies: [{ kind: "component", type: MemberListComponent, selector: "c2g-member-list", inputs: ["members"], outputs: ["memberRemove", "memberInvite", "memberEdit"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1382
|
+
}
|
|
1383
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MemberPanelComponent, decorators: [{
|
|
1384
|
+
type: Component,
|
|
1385
|
+
args: [{ selector: 'c2g-member-panel', standalone: true, imports: [MemberListComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"tw-card\">\n <div class=\"tw-card__header\">\n <h2 class=\"tw-card__title\">Teilnehmer ({{ members().length }})</h2>\n <ng-content select=\"[slot=header-actions]\"></ng-content>\n </div>\n\n <c2g-member-list\n [members]=\"members()\"\n (memberRemove)=\"memberRemove.emit($event)\"\n (memberInvite)=\"memberInvite.emit($event)\"\n (memberEdit)=\"memberEdit.emit($event)\">\n </c2g-member-list>\n</div>\n", styles: [".tw-card{background:var(--c2g-color-surface);border-radius:12px;border:1px solid var(--c2g-color-outline-variant);overflow:hidden}.tw-card__header{display:flex;align-items:center;justify-content:space-between;padding:1.2rem 1.4rem;border-bottom:1px solid var(--c2g-color-neutral-100)}.tw-card__title{margin:0;font-size:.96rem;font-weight:700;color:var(--c2g-color-text-primary)}\n"] }]
|
|
1386
|
+
}], propDecorators: { members: [{ type: i0.Input, args: [{ isSignal: true, alias: "members", required: true }] }], memberRemove: [{ type: i0.Output, args: ["memberRemove"] }], memberInvite: [{ type: i0.Output, args: ["memberInvite"] }], memberEdit: [{ type: i0.Output, args: ["memberEdit"] }] } });
|
|
1387
|
+
|
|
1388
|
+
class MenuComponent {
|
|
1389
|
+
items = input.required(...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
1390
|
+
triggerLabel = input('Menü', ...(ngDevMode ? [{ debugName: "triggerLabel" }] : []));
|
|
1391
|
+
triggerAriaLabel = input('Menü öffnen', ...(ngDevMode ? [{ debugName: "triggerAriaLabel" }] : []));
|
|
1392
|
+
itemSelected = output();
|
|
1393
|
+
flatItems = computed(() => this.items(), ...(ngDevMode ? [{ debugName: "flatItems" }] : []));
|
|
1394
|
+
select(item) {
|
|
1395
|
+
if (item.disabled || (item.children?.length ?? 0) > 0)
|
|
1396
|
+
return;
|
|
1397
|
+
this.itemSelected.emit({ item });
|
|
1398
|
+
}
|
|
1399
|
+
selectChild(parent, child) {
|
|
1400
|
+
if (child.disabled)
|
|
1401
|
+
return;
|
|
1402
|
+
this.itemSelected.emit({ item: child, parent });
|
|
1403
|
+
}
|
|
1404
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1405
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: MenuComponent, isStandalone: true, selector: "c2g-menu", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, triggerLabel: { classPropertyName: "triggerLabel", publicName: "triggerLabel", isSignal: true, isRequired: false, transformFunction: null }, triggerAriaLabel: { classPropertyName: "triggerAriaLabel", publicName: "triggerAriaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemSelected: "itemSelected" }, ngImport: i0, template: "<div class=\"c2g-menu\">\n <button\n class=\"c2g-menu__trigger\"\n type=\"button\"\n [attr.aria-label]=\"triggerAriaLabel()\"\n [cdkMenuTriggerFor]=\"menuPanel\">\n {{ triggerLabel() }}\n </button>\n\n <ng-template #menuPanel>\n <ul class=\"c2g-menu__list\" cdkMenu>\n @for (item of flatItems(); track item.key) {\n <li\n class=\"c2g-menu__entry\"\n [class.c2g-menu__entry--has-children]=\"(item.children?.length ?? 0) > 0\">\n\n @if ((item.children?.length ?? 0) > 0) {\n <button\n class=\"c2g-menu__item\"\n cdkMenuItem\n [cdkMenuTriggerFor]=\"subMenu\"\n [disabled]=\"item.disabled ?? false\"\n type=\"button\">\n @if (item.icon) {\n <span class=\"c2g-menu__icon\" aria-hidden=\"true\">{{ item.icon }}</span>\n }\n <span>{{ item.label }}</span>\n <span class=\"c2g-menu__caret\" aria-hidden=\"true\">\u203A</span>\n </button>\n\n <ng-template #subMenu>\n <ul class=\"c2g-menu__submenu\" cdkMenu>\n @for (child of item.children ?? []; track child.key) {\n <li role=\"none\">\n <button\n class=\"c2g-menu__item\"\n [class.c2g-menu__item--danger]=\"child.variant === 'danger'\"\n [disabled]=\"child.disabled ?? false\"\n cdkMenuItem\n type=\"button\"\n (cdkMenuItemTriggered)=\"selectChild(item, child)\">\n @if (child.icon) {\n <span class=\"c2g-menu__icon\" aria-hidden=\"true\">{{ child.icon }}</span>\n }\n <span>{{ child.label }}</span>\n </button>\n </li>\n }\n </ul>\n </ng-template>\n } @else {\n <button\n class=\"c2g-menu__item\"\n [class.c2g-menu__item--danger]=\"item.variant === 'danger'\"\n [disabled]=\"item.disabled ?? false\"\n cdkMenuItem\n type=\"button\"\n (cdkMenuItemTriggered)=\"select(item)\">\n @if (item.icon) {\n <span class=\"c2g-menu__icon\" aria-hidden=\"true\">{{ item.icon }}</span>\n }\n <span>{{ item.label }}</span>\n </button>\n }\n </li>\n }\n </ul>\n </ng-template>\n</div>\n", styles: [".c2g-menu{position:relative;display:inline-flex}.c2g-menu__trigger{display:inline-flex;align-items:center;justify-content:center;min-height:2rem;padding:.35rem .75rem;border:1px solid var(--c2g-color-outline);border-radius:.5rem;background:var(--c2g-color-surface);color:var(--c2g-color-text-primary);font-size:.875rem;font-weight:600;cursor:pointer}.c2g-menu__trigger:hover{background:var(--c2g-color-neutral-100)}.c2g-menu__trigger:focus-visible{outline:2px solid var(--c2g-color-secondary);outline-offset:2px}.c2g-menu__list,.c2g-menu__submenu{margin:0;padding:.25rem;list-style:none;background:var(--c2g-color-surface);border:1px solid var(--c2g-color-outline-variant);border-radius:.625rem;box-shadow:0 8px 22px #1018281f}.c2g-menu__list{position:absolute;top:calc(100% + .35rem);left:0;min-width:13rem;z-index:100}.c2g-menu__entry{position:relative}.c2g-menu__entry--active>.c2g-menu__item,.c2g-menu__item:hover:not([disabled]){background:var(--c2g-color-secondary-container)}.c2g-menu__item{width:100%;display:flex;align-items:center;gap:.5rem;border:0;border-radius:.45rem;background:transparent;color:var(--c2g-color-text-primary);font-size:.84rem;font-weight:500;padding:.5rem .65rem;text-align:left;cursor:pointer}.c2g-menu__item:focus-visible{outline:2px solid var(--c2g-color-secondary);outline-offset:-2px}.c2g-menu__item[disabled]{opacity:.45;cursor:not-allowed}.c2g-menu__icon{font-size:.95rem;line-height:1}.c2g-menu__caret{margin-left:auto;color:var(--c2g-color-text-muted)}.c2g-menu__item--danger{color:var(--c2g-color-error)}.c2g-menu__item--danger:hover:not([disabled]){background:var(--c2g-color-primary-light)}.c2g-menu__submenu{position:absolute;top:-.25rem;left:calc(100% + .2rem);min-width:11rem;z-index:110;display:none}.c2g-menu__entry--has-children:hover>.c2g-menu__submenu,.c2g-menu__entry--has-children.c2g-menu__entry--active>.c2g-menu__submenu{display:block}\n"], dependencies: [{ kind: "directive", type: CdkMenu, selector: "[cdkMenu]", outputs: ["closed"], exportAs: ["cdkMenu"] }, { kind: "directive", type: CdkMenuItem, selector: "[cdkMenuItem]", inputs: ["cdkMenuItemDisabled", "cdkMenuitemTypeaheadLabel"], outputs: ["cdkMenuItemTriggered"], exportAs: ["cdkMenuItem"] }, { kind: "directive", type: CdkMenuTrigger, selector: "[cdkMenuTriggerFor]", inputs: ["cdkMenuTriggerFor", "cdkMenuPosition", "cdkMenuTriggerData"], outputs: ["cdkMenuOpened", "cdkMenuClosed"], exportAs: ["cdkMenuTriggerFor"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1406
|
+
}
|
|
1407
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MenuComponent, decorators: [{
|
|
1408
|
+
type: Component,
|
|
1409
|
+
args: [{ selector: 'c2g-menu', standalone: true, imports: [CdkMenu, CdkMenuItem, CdkMenuTrigger], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"c2g-menu\">\n <button\n class=\"c2g-menu__trigger\"\n type=\"button\"\n [attr.aria-label]=\"triggerAriaLabel()\"\n [cdkMenuTriggerFor]=\"menuPanel\">\n {{ triggerLabel() }}\n </button>\n\n <ng-template #menuPanel>\n <ul class=\"c2g-menu__list\" cdkMenu>\n @for (item of flatItems(); track item.key) {\n <li\n class=\"c2g-menu__entry\"\n [class.c2g-menu__entry--has-children]=\"(item.children?.length ?? 0) > 0\">\n\n @if ((item.children?.length ?? 0) > 0) {\n <button\n class=\"c2g-menu__item\"\n cdkMenuItem\n [cdkMenuTriggerFor]=\"subMenu\"\n [disabled]=\"item.disabled ?? false\"\n type=\"button\">\n @if (item.icon) {\n <span class=\"c2g-menu__icon\" aria-hidden=\"true\">{{ item.icon }}</span>\n }\n <span>{{ item.label }}</span>\n <span class=\"c2g-menu__caret\" aria-hidden=\"true\">\u203A</span>\n </button>\n\n <ng-template #subMenu>\n <ul class=\"c2g-menu__submenu\" cdkMenu>\n @for (child of item.children ?? []; track child.key) {\n <li role=\"none\">\n <button\n class=\"c2g-menu__item\"\n [class.c2g-menu__item--danger]=\"child.variant === 'danger'\"\n [disabled]=\"child.disabled ?? false\"\n cdkMenuItem\n type=\"button\"\n (cdkMenuItemTriggered)=\"selectChild(item, child)\">\n @if (child.icon) {\n <span class=\"c2g-menu__icon\" aria-hidden=\"true\">{{ child.icon }}</span>\n }\n <span>{{ child.label }}</span>\n </button>\n </li>\n }\n </ul>\n </ng-template>\n } @else {\n <button\n class=\"c2g-menu__item\"\n [class.c2g-menu__item--danger]=\"item.variant === 'danger'\"\n [disabled]=\"item.disabled ?? false\"\n cdkMenuItem\n type=\"button\"\n (cdkMenuItemTriggered)=\"select(item)\">\n @if (item.icon) {\n <span class=\"c2g-menu__icon\" aria-hidden=\"true\">{{ item.icon }}</span>\n }\n <span>{{ item.label }}</span>\n </button>\n }\n </li>\n }\n </ul>\n </ng-template>\n</div>\n", styles: [".c2g-menu{position:relative;display:inline-flex}.c2g-menu__trigger{display:inline-flex;align-items:center;justify-content:center;min-height:2rem;padding:.35rem .75rem;border:1px solid var(--c2g-color-outline);border-radius:.5rem;background:var(--c2g-color-surface);color:var(--c2g-color-text-primary);font-size:.875rem;font-weight:600;cursor:pointer}.c2g-menu__trigger:hover{background:var(--c2g-color-neutral-100)}.c2g-menu__trigger:focus-visible{outline:2px solid var(--c2g-color-secondary);outline-offset:2px}.c2g-menu__list,.c2g-menu__submenu{margin:0;padding:.25rem;list-style:none;background:var(--c2g-color-surface);border:1px solid var(--c2g-color-outline-variant);border-radius:.625rem;box-shadow:0 8px 22px #1018281f}.c2g-menu__list{position:absolute;top:calc(100% + .35rem);left:0;min-width:13rem;z-index:100}.c2g-menu__entry{position:relative}.c2g-menu__entry--active>.c2g-menu__item,.c2g-menu__item:hover:not([disabled]){background:var(--c2g-color-secondary-container)}.c2g-menu__item{width:100%;display:flex;align-items:center;gap:.5rem;border:0;border-radius:.45rem;background:transparent;color:var(--c2g-color-text-primary);font-size:.84rem;font-weight:500;padding:.5rem .65rem;text-align:left;cursor:pointer}.c2g-menu__item:focus-visible{outline:2px solid var(--c2g-color-secondary);outline-offset:-2px}.c2g-menu__item[disabled]{opacity:.45;cursor:not-allowed}.c2g-menu__icon{font-size:.95rem;line-height:1}.c2g-menu__caret{margin-left:auto;color:var(--c2g-color-text-muted)}.c2g-menu__item--danger{color:var(--c2g-color-error)}.c2g-menu__item--danger:hover:not([disabled]){background:var(--c2g-color-primary-light)}.c2g-menu__submenu{position:absolute;top:-.25rem;left:calc(100% + .2rem);min-width:11rem;z-index:110;display:none}.c2g-menu__entry--has-children:hover>.c2g-menu__submenu,.c2g-menu__entry--has-children.c2g-menu__entry--active>.c2g-menu__submenu{display:block}\n"] }]
|
|
1410
|
+
}], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], triggerLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "triggerLabel", required: false }] }], triggerAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "triggerAriaLabel", required: false }] }], itemSelected: [{ type: i0.Output, args: ["itemSelected"] }] } });
|
|
1411
|
+
|
|
1412
|
+
class SubmenuItemComponent {
|
|
1413
|
+
item = input.required(...(ngDevMode ? [{ debugName: "item" }] : []));
|
|
1414
|
+
active = input(false, ...(ngDevMode ? [{ debugName: "active" }] : []));
|
|
1415
|
+
selected = output();
|
|
1416
|
+
mouseenter = output();
|
|
1417
|
+
onClick() {
|
|
1418
|
+
if (this.item().disabled) {
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
this.selected.emit(this.item());
|
|
1422
|
+
}
|
|
1423
|
+
onMouseEnter() {
|
|
1424
|
+
this.mouseenter.emit();
|
|
1425
|
+
}
|
|
1426
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SubmenuItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1427
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.19", type: SubmenuItemComponent, isStandalone: true, selector: "c2g-submenu-item", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selected", mouseenter: "mouseenter" }, host: { listeners: { "mouseenter": "onMouseEnter()" } }, ngImport: i0, template: "<li role=\"none\">\n <button\n class=\"c2g-submenu-item\"\n [class.c2g-submenu-item--active]=\"active() || item().active\"\n [disabled]=\"item().disabled ?? false\"\n role=\"menuitem\"\n type=\"button\"\n (click)=\"onClick()\">\n {{ item().label }}\n </button>\n</li>\n", styles: [".c2g-submenu-item{width:100%;display:block;border:0;border-radius:.5rem;padding:.45rem .65rem;background:transparent;color:var(--c2g-color-text-primary);text-align:left;font-size:.84rem;font-weight:500;cursor:pointer}.c2g-submenu-item:hover:not([disabled]),.c2g-submenu-item.c2g-submenu-item--active{background:var(--c2g-color-secondary-container)}.c2g-submenu-item:focus-visible{outline:2px solid var(--c2g-color-secondary);outline-offset:-2px}.c2g-submenu-item[disabled]{opacity:.45;cursor:not-allowed}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1428
|
+
}
|
|
1429
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SubmenuItemComponent, decorators: [{
|
|
1430
|
+
type: Component,
|
|
1431
|
+
args: [{ selector: 'c2g-submenu-item', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<li role=\"none\">\n <button\n class=\"c2g-submenu-item\"\n [class.c2g-submenu-item--active]=\"active() || item().active\"\n [disabled]=\"item().disabled ?? false\"\n role=\"menuitem\"\n type=\"button\"\n (click)=\"onClick()\">\n {{ item().label }}\n </button>\n</li>\n", styles: [".c2g-submenu-item{width:100%;display:block;border:0;border-radius:.5rem;padding:.45rem .65rem;background:transparent;color:var(--c2g-color-text-primary);text-align:left;font-size:.84rem;font-weight:500;cursor:pointer}.c2g-submenu-item:hover:not([disabled]),.c2g-submenu-item.c2g-submenu-item--active{background:var(--c2g-color-secondary-container)}.c2g-submenu-item:focus-visible{outline:2px solid var(--c2g-color-secondary);outline-offset:-2px}.c2g-submenu-item[disabled]{opacity:.45;cursor:not-allowed}\n"] }]
|
|
1432
|
+
}], propDecorators: { item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: true }] }], active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }], selected: [{ type: i0.Output, args: ["selected"] }], mouseenter: [{ type: i0.Output, args: ["mouseenter"] }], onMouseEnter: [{
|
|
1433
|
+
type: HostListener,
|
|
1434
|
+
args: ['mouseenter']
|
|
1435
|
+
}] } });
|
|
1436
|
+
|
|
1437
|
+
class SubmenuComponent {
|
|
1438
|
+
items = input.required(...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
1439
|
+
open = input(false, ...(ngDevMode ? [{ debugName: "open" }] : []));
|
|
1440
|
+
itemSelected = output();
|
|
1441
|
+
activeIndex = signal(0, ...(ngDevMode ? [{ debugName: "activeIndex" }] : []));
|
|
1442
|
+
onItemSelected(item) {
|
|
1443
|
+
if (item.disabled) {
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
this.itemSelected.emit(item);
|
|
1447
|
+
}
|
|
1448
|
+
setActiveIndex(index) {
|
|
1449
|
+
this.activeIndex.set(index);
|
|
1450
|
+
}
|
|
1451
|
+
onKeydown(event) {
|
|
1452
|
+
const list = this.items();
|
|
1453
|
+
if (!list.length) {
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
1456
|
+
if (event.key === 'ArrowDown') {
|
|
1457
|
+
event.preventDefault();
|
|
1458
|
+
this.activeIndex.set((this.activeIndex() + 1) % list.length);
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
if (event.key === 'ArrowUp') {
|
|
1462
|
+
event.preventDefault();
|
|
1463
|
+
this.activeIndex.set((this.activeIndex() - 1 + list.length) % list.length);
|
|
1464
|
+
return;
|
|
1465
|
+
}
|
|
1466
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
1467
|
+
event.preventDefault();
|
|
1468
|
+
const selected = list[this.activeIndex()];
|
|
1469
|
+
this.onItemSelected(selected);
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SubmenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1473
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: SubmenuComponent, isStandalone: true, selector: "c2g-submenu", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemSelected: "itemSelected" }, ngImport: i0, template: "@if (open()) {\n <ul class=\"c2g-submenu\" role=\"menu\" (keydown)=\"onKeydown($event)\">\n @for (item of items(); track item.key; let index = $index) {\n <c2g-submenu-item\n [item]=\"item\"\n [active]=\"activeIndex() === index\"\n (mouseenter)=\"setActiveIndex(index)\"\n (selected)=\"onItemSelected($event)\">\n </c2g-submenu-item>\n }\n </ul>\n}\n", styles: [".c2g-submenu{position:absolute;top:calc(100% + .35rem);left:0;min-width:12rem;margin:0;padding:.25rem;list-style:none;border:1px solid var(--c2g-color-outline-variant);border-radius:.7rem;background:var(--c2g-color-surface);box-shadow:0 10px 25px #10182824;z-index:120}\n"], dependencies: [{ kind: "component", type: SubmenuItemComponent, selector: "c2g-submenu-item", inputs: ["item", "active"], outputs: ["selected", "mouseenter"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1474
|
+
}
|
|
1475
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SubmenuComponent, decorators: [{
|
|
1476
|
+
type: Component,
|
|
1477
|
+
args: [{ selector: 'c2g-submenu', standalone: true, imports: [SubmenuItemComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (open()) {\n <ul class=\"c2g-submenu\" role=\"menu\" (keydown)=\"onKeydown($event)\">\n @for (item of items(); track item.key; let index = $index) {\n <c2g-submenu-item\n [item]=\"item\"\n [active]=\"activeIndex() === index\"\n (mouseenter)=\"setActiveIndex(index)\"\n (selected)=\"onItemSelected($event)\">\n </c2g-submenu-item>\n }\n </ul>\n}\n", styles: [".c2g-submenu{position:absolute;top:calc(100% + .35rem);left:0;min-width:12rem;margin:0;padding:.25rem;list-style:none;border:1px solid var(--c2g-color-outline-variant);border-radius:.7rem;background:var(--c2g-color-surface);box-shadow:0 10px 25px #10182824;z-index:120}\n"] }]
|
|
1478
|
+
}], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], itemSelected: [{ type: i0.Output, args: ["itemSelected"] }] } });
|
|
1479
|
+
|
|
1480
|
+
class MainNavigationItemComponent {
|
|
1481
|
+
item = input.required(...(ngDevMode ? [{ debugName: "item" }] : []));
|
|
1482
|
+
itemSelected = output();
|
|
1483
|
+
subItemSelected = output();
|
|
1484
|
+
submenuOpen = signal(false, ...(ngDevMode ? [{ debugName: "submenuOpen" }] : []));
|
|
1485
|
+
hasSubmenu = computed(() => (this.item().subItems?.length ?? 0) > 0, ...(ngDevMode ? [{ debugName: "hasSubmenu" }] : []));
|
|
1486
|
+
onItemClick() {
|
|
1487
|
+
if (this.item().disabled) {
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
if (this.hasSubmenu()) {
|
|
1491
|
+
this.submenuOpen.update(open => !open);
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
this.itemSelected.emit(this.item());
|
|
1495
|
+
}
|
|
1496
|
+
onMouseEnter() {
|
|
1497
|
+
if (this.hasSubmenu()) {
|
|
1498
|
+
this.submenuOpen.set(true);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
onMouseLeave() {
|
|
1502
|
+
this.submenuOpen.set(false);
|
|
1503
|
+
}
|
|
1504
|
+
onSubItemSelected(sub) {
|
|
1505
|
+
this.subItemSelected.emit({ main: this.item(), sub });
|
|
1506
|
+
this.submenuOpen.set(false);
|
|
1507
|
+
}
|
|
1508
|
+
onItemKeydown(event) {
|
|
1509
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
1510
|
+
event.preventDefault();
|
|
1511
|
+
this.onItemClick();
|
|
1512
|
+
return;
|
|
1513
|
+
}
|
|
1514
|
+
if (event.key === 'ArrowDown' && this.hasSubmenu()) {
|
|
1515
|
+
event.preventDefault();
|
|
1516
|
+
this.submenuOpen.set(true);
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
if (event.key === 'Escape') {
|
|
1520
|
+
event.preventDefault();
|
|
1521
|
+
this.submenuOpen.set(false);
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MainNavigationItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1525
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: MainNavigationItemComponent, isStandalone: true, selector: "c2g-main-navigation-item", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { itemSelected: "itemSelected", subItemSelected: "subItemSelected" }, ngImport: i0, template: "<li class=\"c2g-main-nav-item\" (mouseenter)=\"onMouseEnter()\" (mouseleave)=\"onMouseLeave()\">\n <button\n class=\"c2g-main-nav-item__button\"\n [class.c2g-main-nav-item__button--active]=\"item().active\"\n [disabled]=\"item().disabled ?? false\"\n [attr.aria-haspopup]=\"hasSubmenu() ? 'menu' : null\"\n [attr.aria-expanded]=\"hasSubmenu() ? submenuOpen() : null\"\n type=\"button\"\n (click)=\"onItemClick()\"\n (keydown)=\"onItemKeydown($event)\">\n <span>{{ item().label }}</span>\n @if (hasSubmenu()) {\n <span class=\"c2g-main-nav-item__caret\" aria-hidden=\"true\">\u25BE</span>\n }\n </button>\n\n @if (hasSubmenu()) {\n <c2g-submenu\n [items]=\"item().subItems ?? []\"\n [open]=\"submenuOpen()\"\n (itemSelected)=\"onSubItemSelected($event)\">\n </c2g-submenu>\n }\n</li>\n", styles: [".c2g-main-nav-item{position:relative}.c2g-main-nav-item__button{display:inline-flex;align-items:center;gap:.35rem;min-height:2.2rem;padding:.4rem .8rem;border:1px solid transparent;border-radius:.6rem;background:transparent;color:var(--c2g-color-text-primary);font-size:.9rem;font-weight:600;cursor:pointer}.c2g-main-nav-item__button:hover:not([disabled]){background:var(--c2g-color-neutral-100);border-color:var(--c2g-color-outline-variant)}.c2g-main-nav-item__button:focus-visible{outline:2px solid var(--c2g-color-secondary);outline-offset:2px}.c2g-main-nav-item__button[disabled]{opacity:.5;cursor:not-allowed}.c2g-main-nav-item__button--active{background:var(--c2g-color-secondary-container);border-color:var(--c2g-color-outline);color:var(--c2g-color-secondary-dark)}.c2g-main-nav-item__caret{font-size:.7rem;color:var(--c2g-color-text-muted)}\n"], dependencies: [{ kind: "component", type: SubmenuComponent, selector: "c2g-submenu", inputs: ["items", "open"], outputs: ["itemSelected"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1526
|
+
}
|
|
1527
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MainNavigationItemComponent, decorators: [{
|
|
1528
|
+
type: Component,
|
|
1529
|
+
args: [{ selector: 'c2g-main-navigation-item', standalone: true, imports: [SubmenuComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<li class=\"c2g-main-nav-item\" (mouseenter)=\"onMouseEnter()\" (mouseleave)=\"onMouseLeave()\">\n <button\n class=\"c2g-main-nav-item__button\"\n [class.c2g-main-nav-item__button--active]=\"item().active\"\n [disabled]=\"item().disabled ?? false\"\n [attr.aria-haspopup]=\"hasSubmenu() ? 'menu' : null\"\n [attr.aria-expanded]=\"hasSubmenu() ? submenuOpen() : null\"\n type=\"button\"\n (click)=\"onItemClick()\"\n (keydown)=\"onItemKeydown($event)\">\n <span>{{ item().label }}</span>\n @if (hasSubmenu()) {\n <span class=\"c2g-main-nav-item__caret\" aria-hidden=\"true\">\u25BE</span>\n }\n </button>\n\n @if (hasSubmenu()) {\n <c2g-submenu\n [items]=\"item().subItems ?? []\"\n [open]=\"submenuOpen()\"\n (itemSelected)=\"onSubItemSelected($event)\">\n </c2g-submenu>\n }\n</li>\n", styles: [".c2g-main-nav-item{position:relative}.c2g-main-nav-item__button{display:inline-flex;align-items:center;gap:.35rem;min-height:2.2rem;padding:.4rem .8rem;border:1px solid transparent;border-radius:.6rem;background:transparent;color:var(--c2g-color-text-primary);font-size:.9rem;font-weight:600;cursor:pointer}.c2g-main-nav-item__button:hover:not([disabled]){background:var(--c2g-color-neutral-100);border-color:var(--c2g-color-outline-variant)}.c2g-main-nav-item__button:focus-visible{outline:2px solid var(--c2g-color-secondary);outline-offset:2px}.c2g-main-nav-item__button[disabled]{opacity:.5;cursor:not-allowed}.c2g-main-nav-item__button--active{background:var(--c2g-color-secondary-container);border-color:var(--c2g-color-outline);color:var(--c2g-color-secondary-dark)}.c2g-main-nav-item__caret{font-size:.7rem;color:var(--c2g-color-text-muted)}\n"] }]
|
|
1530
|
+
}], propDecorators: { item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: true }] }], itemSelected: [{ type: i0.Output, args: ["itemSelected"] }], subItemSelected: [{ type: i0.Output, args: ["subItemSelected"] }] } });
|
|
1531
|
+
|
|
1532
|
+
class MainNavigationComponent {
|
|
1533
|
+
items = input.required(...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
1534
|
+
ariaLabel = input('Hauptnavigation', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
|
|
1535
|
+
mainItemSelected = output();
|
|
1536
|
+
subItemSelected = output();
|
|
1537
|
+
onMainItemSelected(item) {
|
|
1538
|
+
this.mainItemSelected.emit(item);
|
|
1539
|
+
}
|
|
1540
|
+
onSubItemSelected(selection) {
|
|
1541
|
+
this.subItemSelected.emit(selection);
|
|
1542
|
+
}
|
|
1543
|
+
trackByKey = (_index, item) => item.key;
|
|
1544
|
+
trackSubByKey = (_index, item) => item.key;
|
|
1545
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MainNavigationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1546
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: MainNavigationComponent, isStandalone: true, selector: "c2g-main-navigation", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { mainItemSelected: "mainItemSelected", subItemSelected: "subItemSelected" }, ngImport: i0, template: "<nav class=\"c2g-main-navigation\" [attr.aria-label]=\"ariaLabel()\">\n <ul class=\"c2g-main-navigation__list\">\n @for (item of items(); track trackByKey($index, item)) {\n <c2g-main-navigation-item\n [item]=\"item\"\n (itemSelected)=\"onMainItemSelected($event)\"\n (subItemSelected)=\"onSubItemSelected($event)\">\n </c2g-main-navigation-item>\n }\n </ul>\n</nav>\n", styles: [".c2g-main-navigation{width:100%}.c2g-main-navigation__list{display:flex;align-items:center;gap:.5rem;list-style:none;margin:0;padding:0;flex-wrap:wrap}\n"], dependencies: [{ kind: "component", type: MainNavigationItemComponent, selector: "c2g-main-navigation-item", inputs: ["item"], outputs: ["itemSelected", "subItemSelected"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1547
|
+
}
|
|
1548
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MainNavigationComponent, decorators: [{
|
|
1549
|
+
type: Component,
|
|
1550
|
+
args: [{ selector: 'c2g-main-navigation', standalone: true, imports: [MainNavigationItemComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<nav class=\"c2g-main-navigation\" [attr.aria-label]=\"ariaLabel()\">\n <ul class=\"c2g-main-navigation__list\">\n @for (item of items(); track trackByKey($index, item)) {\n <c2g-main-navigation-item\n [item]=\"item\"\n (itemSelected)=\"onMainItemSelected($event)\"\n (subItemSelected)=\"onSubItemSelected($event)\">\n </c2g-main-navigation-item>\n }\n </ul>\n</nav>\n", styles: [".c2g-main-navigation{width:100%}.c2g-main-navigation__list{display:flex;align-items:center;gap:.5rem;list-style:none;margin:0;padding:0;flex-wrap:wrap}\n"] }]
|
|
1551
|
+
}], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], mainItemSelected: [{ type: i0.Output, args: ["mainItemSelected"] }], subItemSelected: [{ type: i0.Output, args: ["subItemSelected"] }] } });
|
|
1552
|
+
|
|
1553
|
+
const WEATHER_ICONS = {
|
|
1554
|
+
sunny: '☀️', 'partly-cloudy': '⛅', cloudy: '☁️',
|
|
1555
|
+
rain: '🌧️', storm: '⛈️', snow: '❄️', wind: '💨', fog: '🌫️'
|
|
1556
|
+
};
|
|
1557
|
+
const TOUR_TYPE_ICONS = {
|
|
1558
|
+
hiking: '🥾', camping: '⛺', cycling: '🚴', climbing: '🧗',
|
|
1559
|
+
kayaking: '🚣', skiing: '⛷️', backpacking: '🎒', road_trip: '🚗'
|
|
1560
|
+
};
|
|
1561
|
+
class NextAdventureWidgetComponent {
|
|
1562
|
+
data = input.required(...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
1563
|
+
countdown = computed(() => {
|
|
1564
|
+
const from = new Date(this.data().fromDate);
|
|
1565
|
+
from.setHours(0, 0, 0, 0);
|
|
1566
|
+
const today = new Date();
|
|
1567
|
+
today.setHours(0, 0, 0, 0);
|
|
1568
|
+
return Math.max(0, Math.ceil((from.getTime() - today.getTime()) / 86400000));
|
|
1569
|
+
}, ...(ngDevMode ? [{ debugName: "countdown" }] : []));
|
|
1570
|
+
duration = computed(() => {
|
|
1571
|
+
const from = new Date(this.data().fromDate);
|
|
1572
|
+
const to = new Date(this.data().toDate);
|
|
1573
|
+
return Math.ceil((to.getTime() - from.getTime()) / 86400000) + 1;
|
|
1574
|
+
}, ...(ngDevMode ? [{ debugName: "duration" }] : []));
|
|
1575
|
+
countdownLabel = computed(() => {
|
|
1576
|
+
const d = this.countdown();
|
|
1577
|
+
if (d === 0)
|
|
1578
|
+
return 'Heute gehts los!';
|
|
1579
|
+
if (d === 1)
|
|
1580
|
+
return 'Morgen gehts los!';
|
|
1581
|
+
return `Noch ${d} Tage`;
|
|
1582
|
+
}, ...(ngDevMode ? [{ debugName: "countdownLabel" }] : []));
|
|
1583
|
+
dateRange = computed(() => {
|
|
1584
|
+
const fmt = (s) => new Date(s).toLocaleDateString('de-DE', { day: '2-digit', month: 'short' });
|
|
1585
|
+
return `${fmt(this.data().fromDate)} – ${fmt(this.data().toDate)}`;
|
|
1586
|
+
}, ...(ngDevMode ? [{ debugName: "dateRange" }] : []));
|
|
1587
|
+
tourTypeIcon = computed(() => TOUR_TYPE_ICONS[this.data().tourType ?? ''] ?? '🏕️', ...(ngDevMode ? [{ debugName: "tourTypeIcon" }] : []));
|
|
1588
|
+
weatherSlots = computed(() => (this.data().weather ?? []).slice(0, 4), ...(ngDevMode ? [{ debugName: "weatherSlots" }] : []));
|
|
1589
|
+
urgencyClass = computed(() => {
|
|
1590
|
+
const d = this.countdown();
|
|
1591
|
+
if (d <= 1)
|
|
1592
|
+
return 'c2g-next-adventure--now';
|
|
1593
|
+
if (d <= 7)
|
|
1594
|
+
return 'c2g-next-adventure--soon';
|
|
1595
|
+
return '';
|
|
1596
|
+
}, ...(ngDevMode ? [{ debugName: "urgencyClass" }] : []));
|
|
1597
|
+
weatherIcon(icon) {
|
|
1598
|
+
return WEATHER_ICONS[icon] ?? '🌤️';
|
|
1599
|
+
}
|
|
1600
|
+
formatWeatherDate(date) {
|
|
1601
|
+
return new Date(date).toLocaleDateString('de-DE', { weekday: 'short', day: 'numeric' });
|
|
1602
|
+
}
|
|
1603
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NextAdventureWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1604
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: NextAdventureWidgetComponent, isStandalone: true, selector: "c2g-next-adventure-widget", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"c2g-next-adventure\" [class]=\"urgencyClass()\">\n\n <!-- Row 1: type icon + meta + optional pulse orb -->\n <div class=\"c2g-next-adventure__header\">\n <span class=\"c2g-next-adventure__type-icon\" aria-hidden=\"true\">{{ tourTypeIcon() }}</span>\n\n <div class=\"c2g-next-adventure__meta\">\n <span class=\"c2g-next-adventure__label\">N\u00E4chstes Abenteuer</span>\n <span class=\"c2g-next-adventure__dates\">{{ dateRange() }} \u00B7 {{ duration() }} Tage</span>\n </div>\n\n @if (data().showPulse) {\n <div class=\"c2g-next-adventure__pulse-orb\" aria-hidden=\"true\">\n <span class=\"c2g-next-adventure__pulse-ring c2g-next-adventure__pulse-ring--1\"></span>\n <span class=\"c2g-next-adventure__pulse-ring c2g-next-adventure__pulse-ring--2\"></span>\n <span class=\"c2g-next-adventure__pulse-ring c2g-next-adventure__pulse-ring--3\"></span>\n <span class=\"c2g-next-adventure__pulse-core\">\uD83D\uDD34</span>\n </div>\n }\n </div>\n\n <!-- Tour name + destination -->\n <div class=\"c2g-next-adventure__body\">\n <h2 class=\"c2g-next-adventure__name\">{{ data().tourName }}</h2>\n <p class=\"c2g-next-adventure__destination\">\uD83D\uDCCD {{ data().destination }}</p>\n </div>\n\n <!-- Countdown -->\n <div class=\"c2g-next-adventure__countdown\">\n <span class=\"c2g-next-adventure__countdown-number\">{{ countdown() }}</span>\n <span class=\"c2g-next-adventure__countdown-label\">{{ countdownLabel() }}</span>\n </div>\n\n <!-- Weather strip -->\n @if (weatherSlots().length > 0) {\n <div class=\"c2g-next-adventure__weather\">\n @for (day of weatherSlots(); track day.date) {\n <div class=\"c2g-next-adventure__weather-day\">\n <span class=\"c2g-next-adventure__weather-date\">{{ formatWeatherDate(day.date) }}</span>\n <span class=\"c2g-next-adventure__weather-icon\" aria-hidden=\"true\">{{ weatherIcon(day.icon) }}</span>\n <span class=\"c2g-next-adventure__weather-temp\">\n {{ day.tempMax }}\u00B0 <span class=\"c2g-next-adventure__weather-min\">{{ day.tempMin }}\u00B0</span>\n </span>\n </div>\n }\n </div>\n }\n\n</div>\n", styles: [":host{display:block}.c2g-next-adventure{--_bg-from: var(--c2g-theme-primary, #ff6b35);--_bg-to: var(--c2g-theme-primary-dark, #c0391a);--_shadow-color: var(--c2g-theme-primary-shadow, rgba(255, 107, 53, .35));--_shadow-hover: var(--c2g-theme-primary-shadow-lg, rgba(255, 107, 53, .45));position:relative;overflow:hidden;border-radius:var(--c2g-radius-xl, 20px);padding:22px 22px 0;background:linear-gradient(135deg,var(--_bg-from) 0%,var(--_bg-to) 100%);color:var(--c2g-theme-on-primary, #fff);font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif);box-shadow:0 8px 32px var(--_shadow-color),0 1px #ffffff1a inset;min-height:260px;display:flex;flex-direction:column;gap:14px;transition:box-shadow .25s ease,transform .2s ease}.c2g-next-adventure:hover{box-shadow:0 12px 48px var(--_shadow-hover),0 1px #ffffff1f inset;transform:translateY(-2px)}.c2g-next-adventure:before{content:\"\";position:absolute;top:-50px;right:-50px;width:220px;height:220px;border-radius:50%;background:#ffffff12;pointer-events:none;animation:c2g-adv-blob 6s ease-in-out infinite}.c2g-next-adventure:after{content:\"\";position:absolute;bottom:-70px;left:30px;width:180px;height:180px;border-radius:50%;background:#ffffff0a;pointer-events:none;animation:c2g-adv-blob 8s ease-in-out infinite reverse}.c2g-next-adventure--soon{--_bg-from: var(--c2g-theme-primary-soon, #ff8c42);--_bg-to: var(--c2g-theme-primary-soon-dark, #e05a10);--_shadow-color: var(--c2g-theme-primary-soon-shadow, rgba(255, 140, 66, .4))}.c2g-next-adventure--now{--_bg-from: var(--c2g-theme-tertiary, #4ecdc4);--_bg-to: var(--c2g-theme-tertiary-dark, #2d9e96);--_shadow-color: var(--c2g-theme-tertiary-shadow, rgba(78, 205, 196, .45));animation:c2g-adv-now-pulse 2.5s ease-in-out infinite}@keyframes c2g-adv-blob{0%,to{transform:scale(1) translate(0)}50%{transform:scale(1.1) translate(-8px,8px)}}@keyframes c2g-adv-now-pulse{0%,to{box-shadow:0 8px 32px var(--_shadow-color)}50%{box-shadow:0 12px 48px var(--_shadow-hover, var(--_shadow-color))}}.c2g-next-adventure__header{display:flex;align-items:center;gap:10px;position:relative;z-index:1}.c2g-next-adventure__type-icon{font-size:2.2rem;line-height:1;filter:drop-shadow(0 2px 6px rgba(0,0,0,.25));flex-shrink:0}.c2g-next-adventure__meta{flex:1;display:flex;flex-direction:column;gap:2px}.c2g-next-adventure__label{font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.1em;opacity:.75}.c2g-next-adventure__dates{font-size:.82rem;font-weight:500;opacity:.9}.c2g-next-adventure__pulse-orb{position:relative;width:36px;height:36px;flex-shrink:0;display:flex;align-items:center;justify-content:center}.c2g-next-adventure__pulse-ring{position:absolute;border-radius:50%;border:2px solid color-mix(in srgb,var(--c2g-theme-on-primary, #fff) 80%,transparent);animation:c2g-pulse-ring 2.4s ease-out infinite}.c2g-next-adventure__pulse-ring--1{width:12px;height:12px;animation-delay:0s}.c2g-next-adventure__pulse-ring--2{width:12px;height:12px;animation-delay:.6s}.c2g-next-adventure__pulse-ring--3{width:12px;height:12px;animation-delay:1.2s}.c2g-next-adventure__pulse-core{font-size:.85rem;line-height:1;position:relative;z-index:1}@keyframes c2g-pulse-ring{0%{transform:scale(1);opacity:.9}to{transform:scale(3.2);opacity:0}}.c2g-next-adventure__body{position:relative;z-index:1;flex:1}.c2g-next-adventure__name{font-size:1.55rem;font-weight:900;margin:0 0 5px;line-height:1.15;text-shadow:0 1px 4px rgba(0,0,0,.15);letter-spacing:-.01em}.c2g-next-adventure__destination{font-size:.875rem;margin:0;opacity:.88}.c2g-next-adventure__countdown{position:relative;z-index:1;display:flex;align-items:baseline;gap:8px}.c2g-next-adventure__countdown-number{font-size:3.2rem;font-weight:900;line-height:1;letter-spacing:-.03em;text-shadow:0 3px 12px rgba(0,0,0,.2)}.c2g-next-adventure--now .c2g-next-adventure__countdown-number{font-size:1.6rem}.c2g-next-adventure__countdown-label{font-size:.9rem;font-weight:600;opacity:.88}.c2g-next-adventure__weather{position:relative;z-index:1;display:flex;gap:0;background:color-mix(in srgb,var(--c2g-theme-on-primary, #fff) 13%,transparent);border-top:1px solid color-mix(in srgb,var(--c2g-theme-on-primary, #fff) 12%,transparent);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);margin:0 -22px}.c2g-next-adventure__weather-day{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px;padding:11px 8px;border-right:1px solid color-mix(in srgb,var(--c2g-theme-on-primary, #fff) 10%,transparent);transition:background .15s ease}.c2g-next-adventure__weather-day:last-child{border-right:none}.c2g-next-adventure__weather-day:hover{background:color-mix(in srgb,var(--c2g-theme-on-primary, #fff) 7%,transparent)}.c2g-next-adventure__weather-date{font-size:.62rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em;opacity:.75}.c2g-next-adventure__weather-icon{font-size:1.3rem;line-height:1}.c2g-next-adventure__weather-temp{font-size:.8rem;font-weight:700}.c2g-next-adventure__weather-min{font-weight:400;opacity:.65}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1605
|
+
}
|
|
1606
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: NextAdventureWidgetComponent, decorators: [{
|
|
1607
|
+
type: Component,
|
|
1608
|
+
args: [{ selector: 'c2g-next-adventure-widget', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"c2g-next-adventure\" [class]=\"urgencyClass()\">\n\n <!-- Row 1: type icon + meta + optional pulse orb -->\n <div class=\"c2g-next-adventure__header\">\n <span class=\"c2g-next-adventure__type-icon\" aria-hidden=\"true\">{{ tourTypeIcon() }}</span>\n\n <div class=\"c2g-next-adventure__meta\">\n <span class=\"c2g-next-adventure__label\">N\u00E4chstes Abenteuer</span>\n <span class=\"c2g-next-adventure__dates\">{{ dateRange() }} \u00B7 {{ duration() }} Tage</span>\n </div>\n\n @if (data().showPulse) {\n <div class=\"c2g-next-adventure__pulse-orb\" aria-hidden=\"true\">\n <span class=\"c2g-next-adventure__pulse-ring c2g-next-adventure__pulse-ring--1\"></span>\n <span class=\"c2g-next-adventure__pulse-ring c2g-next-adventure__pulse-ring--2\"></span>\n <span class=\"c2g-next-adventure__pulse-ring c2g-next-adventure__pulse-ring--3\"></span>\n <span class=\"c2g-next-adventure__pulse-core\">\uD83D\uDD34</span>\n </div>\n }\n </div>\n\n <!-- Tour name + destination -->\n <div class=\"c2g-next-adventure__body\">\n <h2 class=\"c2g-next-adventure__name\">{{ data().tourName }}</h2>\n <p class=\"c2g-next-adventure__destination\">\uD83D\uDCCD {{ data().destination }}</p>\n </div>\n\n <!-- Countdown -->\n <div class=\"c2g-next-adventure__countdown\">\n <span class=\"c2g-next-adventure__countdown-number\">{{ countdown() }}</span>\n <span class=\"c2g-next-adventure__countdown-label\">{{ countdownLabel() }}</span>\n </div>\n\n <!-- Weather strip -->\n @if (weatherSlots().length > 0) {\n <div class=\"c2g-next-adventure__weather\">\n @for (day of weatherSlots(); track day.date) {\n <div class=\"c2g-next-adventure__weather-day\">\n <span class=\"c2g-next-adventure__weather-date\">{{ formatWeatherDate(day.date) }}</span>\n <span class=\"c2g-next-adventure__weather-icon\" aria-hidden=\"true\">{{ weatherIcon(day.icon) }}</span>\n <span class=\"c2g-next-adventure__weather-temp\">\n {{ day.tempMax }}\u00B0 <span class=\"c2g-next-adventure__weather-min\">{{ day.tempMin }}\u00B0</span>\n </span>\n </div>\n }\n </div>\n }\n\n</div>\n", styles: [":host{display:block}.c2g-next-adventure{--_bg-from: var(--c2g-theme-primary, #ff6b35);--_bg-to: var(--c2g-theme-primary-dark, #c0391a);--_shadow-color: var(--c2g-theme-primary-shadow, rgba(255, 107, 53, .35));--_shadow-hover: var(--c2g-theme-primary-shadow-lg, rgba(255, 107, 53, .45));position:relative;overflow:hidden;border-radius:var(--c2g-radius-xl, 20px);padding:22px 22px 0;background:linear-gradient(135deg,var(--_bg-from) 0%,var(--_bg-to) 100%);color:var(--c2g-theme-on-primary, #fff);font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif);box-shadow:0 8px 32px var(--_shadow-color),0 1px #ffffff1a inset;min-height:260px;display:flex;flex-direction:column;gap:14px;transition:box-shadow .25s ease,transform .2s ease}.c2g-next-adventure:hover{box-shadow:0 12px 48px var(--_shadow-hover),0 1px #ffffff1f inset;transform:translateY(-2px)}.c2g-next-adventure:before{content:\"\";position:absolute;top:-50px;right:-50px;width:220px;height:220px;border-radius:50%;background:#ffffff12;pointer-events:none;animation:c2g-adv-blob 6s ease-in-out infinite}.c2g-next-adventure:after{content:\"\";position:absolute;bottom:-70px;left:30px;width:180px;height:180px;border-radius:50%;background:#ffffff0a;pointer-events:none;animation:c2g-adv-blob 8s ease-in-out infinite reverse}.c2g-next-adventure--soon{--_bg-from: var(--c2g-theme-primary-soon, #ff8c42);--_bg-to: var(--c2g-theme-primary-soon-dark, #e05a10);--_shadow-color: var(--c2g-theme-primary-soon-shadow, rgba(255, 140, 66, .4))}.c2g-next-adventure--now{--_bg-from: var(--c2g-theme-tertiary, #4ecdc4);--_bg-to: var(--c2g-theme-tertiary-dark, #2d9e96);--_shadow-color: var(--c2g-theme-tertiary-shadow, rgba(78, 205, 196, .45));animation:c2g-adv-now-pulse 2.5s ease-in-out infinite}@keyframes c2g-adv-blob{0%,to{transform:scale(1) translate(0)}50%{transform:scale(1.1) translate(-8px,8px)}}@keyframes c2g-adv-now-pulse{0%,to{box-shadow:0 8px 32px var(--_shadow-color)}50%{box-shadow:0 12px 48px var(--_shadow-hover, var(--_shadow-color))}}.c2g-next-adventure__header{display:flex;align-items:center;gap:10px;position:relative;z-index:1}.c2g-next-adventure__type-icon{font-size:2.2rem;line-height:1;filter:drop-shadow(0 2px 6px rgba(0,0,0,.25));flex-shrink:0}.c2g-next-adventure__meta{flex:1;display:flex;flex-direction:column;gap:2px}.c2g-next-adventure__label{font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.1em;opacity:.75}.c2g-next-adventure__dates{font-size:.82rem;font-weight:500;opacity:.9}.c2g-next-adventure__pulse-orb{position:relative;width:36px;height:36px;flex-shrink:0;display:flex;align-items:center;justify-content:center}.c2g-next-adventure__pulse-ring{position:absolute;border-radius:50%;border:2px solid color-mix(in srgb,var(--c2g-theme-on-primary, #fff) 80%,transparent);animation:c2g-pulse-ring 2.4s ease-out infinite}.c2g-next-adventure__pulse-ring--1{width:12px;height:12px;animation-delay:0s}.c2g-next-adventure__pulse-ring--2{width:12px;height:12px;animation-delay:.6s}.c2g-next-adventure__pulse-ring--3{width:12px;height:12px;animation-delay:1.2s}.c2g-next-adventure__pulse-core{font-size:.85rem;line-height:1;position:relative;z-index:1}@keyframes c2g-pulse-ring{0%{transform:scale(1);opacity:.9}to{transform:scale(3.2);opacity:0}}.c2g-next-adventure__body{position:relative;z-index:1;flex:1}.c2g-next-adventure__name{font-size:1.55rem;font-weight:900;margin:0 0 5px;line-height:1.15;text-shadow:0 1px 4px rgba(0,0,0,.15);letter-spacing:-.01em}.c2g-next-adventure__destination{font-size:.875rem;margin:0;opacity:.88}.c2g-next-adventure__countdown{position:relative;z-index:1;display:flex;align-items:baseline;gap:8px}.c2g-next-adventure__countdown-number{font-size:3.2rem;font-weight:900;line-height:1;letter-spacing:-.03em;text-shadow:0 3px 12px rgba(0,0,0,.2)}.c2g-next-adventure--now .c2g-next-adventure__countdown-number{font-size:1.6rem}.c2g-next-adventure__countdown-label{font-size:.9rem;font-weight:600;opacity:.88}.c2g-next-adventure__weather{position:relative;z-index:1;display:flex;gap:0;background:color-mix(in srgb,var(--c2g-theme-on-primary, #fff) 13%,transparent);border-top:1px solid color-mix(in srgb,var(--c2g-theme-on-primary, #fff) 12%,transparent);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);margin:0 -22px}.c2g-next-adventure__weather-day{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px;padding:11px 8px;border-right:1px solid color-mix(in srgb,var(--c2g-theme-on-primary, #fff) 10%,transparent);transition:background .15s ease}.c2g-next-adventure__weather-day:last-child{border-right:none}.c2g-next-adventure__weather-day:hover{background:color-mix(in srgb,var(--c2g-theme-on-primary, #fff) 7%,transparent)}.c2g-next-adventure__weather-date{font-size:.62rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em;opacity:.75}.c2g-next-adventure__weather-icon{font-size:1.3rem;line-height:1}.c2g-next-adventure__weather-temp{font-size:.8rem;font-weight:700}.c2g-next-adventure__weather-min{font-weight:400;opacity:.65}\n"] }]
|
|
1609
|
+
}], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }] } });
|
|
1610
|
+
|
|
1611
|
+
class PackStatusWidgetComponent {
|
|
1612
|
+
data = input.required(...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
1613
|
+
percent = computed(() => {
|
|
1614
|
+
const d = this.data();
|
|
1615
|
+
if (d.totalItems === 0)
|
|
1616
|
+
return 0;
|
|
1617
|
+
return Math.round((d.completedItems / d.totalItems) * 100);
|
|
1618
|
+
}, ...(ngDevMode ? [{ debugName: "percent" }] : []));
|
|
1619
|
+
criticalOpen = computed(() => this.data().criticalTotal - this.data().criticalCompleted, ...(ngDevMode ? [{ debugName: "criticalOpen" }] : []));
|
|
1620
|
+
statusColor = computed(() => {
|
|
1621
|
+
if (this.percent() === 100)
|
|
1622
|
+
return 'success';
|
|
1623
|
+
if (this.criticalOpen() > 0)
|
|
1624
|
+
return 'warning';
|
|
1625
|
+
return 'primary';
|
|
1626
|
+
}, ...(ngDevMode ? [{ debugName: "statusColor" }] : []));
|
|
1627
|
+
statusLabel = computed(() => {
|
|
1628
|
+
const p = this.percent();
|
|
1629
|
+
if (p === 100)
|
|
1630
|
+
return 'Alles gepackt!';
|
|
1631
|
+
if (p >= 80)
|
|
1632
|
+
return 'Fast fertig';
|
|
1633
|
+
if (p >= 50)
|
|
1634
|
+
return 'Gut dabei';
|
|
1635
|
+
if (p > 0)
|
|
1636
|
+
return 'Packen gestartet';
|
|
1637
|
+
return 'Noch nicht gepackt';
|
|
1638
|
+
}, ...(ngDevMode ? [{ debugName: "statusLabel" }] : []));
|
|
1639
|
+
// SVG ring
|
|
1640
|
+
radius = 44;
|
|
1641
|
+
strokeWidth = 8;
|
|
1642
|
+
circumference = 2 * Math.PI * this.radius;
|
|
1643
|
+
strokeDashoffset = computed(() => this.circumference * (1 - this.percent() / 100), ...(ngDevMode ? [{ debugName: "strokeDashoffset" }] : []));
|
|
1644
|
+
// Segment tick marks (every 10%)
|
|
1645
|
+
ticks = Array.from({ length: 10 }, (_, i) => {
|
|
1646
|
+
const angle = (i / 10) * 360 - 90;
|
|
1647
|
+
const rad = (angle * Math.PI) / 180;
|
|
1648
|
+
const r1 = this.radius + this.strokeWidth / 2 + 3;
|
|
1649
|
+
const r2 = r1 + 4;
|
|
1650
|
+
const cx = 56, cy = 56;
|
|
1651
|
+
return {
|
|
1652
|
+
x1: cx + r1 * Math.cos(rad),
|
|
1653
|
+
y1: cy + r1 * Math.sin(rad),
|
|
1654
|
+
x2: cx + r2 * Math.cos(rad),
|
|
1655
|
+
y2: cy + r2 * Math.sin(rad),
|
|
1656
|
+
};
|
|
1657
|
+
});
|
|
1658
|
+
// Packed items as individual visual "dots" (up to 20 shown)
|
|
1659
|
+
itemDots = computed(() => {
|
|
1660
|
+
const total = Math.min(this.data().totalItems, 20);
|
|
1661
|
+
const done = Math.round((this.data().completedItems / Math.max(this.data().totalItems, 1)) * total);
|
|
1662
|
+
return Array.from({ length: total }, (_, i) => i < done);
|
|
1663
|
+
}, ...(ngDevMode ? [{ debugName: "itemDots" }] : []));
|
|
1664
|
+
// Count-up for percent display
|
|
1665
|
+
displayPercent = signal(0, ...(ngDevMode ? [{ debugName: "displayPercent" }] : []));
|
|
1666
|
+
raf = 0;
|
|
1667
|
+
constructor() {
|
|
1668
|
+
effect(() => {
|
|
1669
|
+
const target = this.percent();
|
|
1670
|
+
cancelAnimationFrame(this.raf);
|
|
1671
|
+
const start = performance.now();
|
|
1672
|
+
const duration = 800;
|
|
1673
|
+
const tick = (now) => {
|
|
1674
|
+
const t = Math.min((now - start) / duration, 1);
|
|
1675
|
+
const ease = 1 - Math.pow(1 - t, 3);
|
|
1676
|
+
this.displayPercent.set(Math.round(ease * target));
|
|
1677
|
+
if (t < 1)
|
|
1678
|
+
this.raf = requestAnimationFrame(tick);
|
|
1679
|
+
};
|
|
1680
|
+
this.raf = requestAnimationFrame(tick);
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: PackStatusWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1684
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: PackStatusWidgetComponent, isStandalone: true, selector: "c2g-pack-status-widget", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"c2g-ps\" [class]=\"'c2g-ps--' + statusColor()\">\n\n <!-- Header -->\n <div class=\"c2g-ps__header\">\n <div class=\"c2g-ps__title-row\">\n <span class=\"c2g-ps__label\">\uD83C\uDF92 Packstatus</span>\n @if (data().tourName) {\n <span class=\"c2g-ps__tour\">{{ data().tourName }}</span>\n }\n </div>\n <span class=\"c2g-ps__status-badge\">{{ statusLabel() }}</span>\n </div>\n\n <!-- Main: ring + info side by side -->\n <div class=\"c2g-ps__body\">\n\n <!-- SVG Ring -->\n <div class=\"c2g-ps__ring-wrap\">\n <svg class=\"c2g-ps__ring\" viewBox=\"0 0 112 112\" aria-hidden=\"true\">\n <!-- Track -->\n <circle class=\"c2g-ps__ring-track\"\n cx=\"56\" cy=\"56\" [attr.r]=\"radius\"\n fill=\"none\" [attr.stroke-width]=\"strokeWidth\" />\n <!-- Fill -->\n <circle class=\"c2g-ps__ring-fill\"\n cx=\"56\" cy=\"56\" [attr.r]=\"radius\"\n fill=\"none\" [attr.stroke-width]=\"strokeWidth\"\n stroke-linecap=\"round\"\n [attr.stroke-dasharray]=\"circumference\"\n [attr.stroke-dashoffset]=\"strokeDashoffset()\"\n transform=\"rotate(-90 56 56)\" />\n <!-- Tick marks -->\n @for (t of ticks; track $index) {\n <line class=\"c2g-ps__tick\"\n [attr.x1]=\"t.x1\" [attr.y1]=\"t.y1\"\n [attr.x2]=\"t.x2\" [attr.y2]=\"t.y2\" />\n }\n </svg>\n <!-- Center content -->\n <div class=\"c2g-ps__ring-center\">\n <span class=\"c2g-ps__percent\">{{ displayPercent() }}<span class=\"c2g-ps__percent-unit\">%</span></span>\n @if (statusColor() === 'success') {\n <span class=\"c2g-ps__ring-emoji\">\uD83C\uDF89</span>\n }\n </div>\n </div>\n\n <!-- Info panel -->\n <div class=\"c2g-ps__info\">\n\n <!-- Item count -->\n <div class=\"c2g-ps__count-row\">\n <span class=\"c2g-ps__count-main\">{{ data().completedItems }}</span>\n <span class=\"c2g-ps__count-sep\">/</span>\n <span class=\"c2g-ps__count-total\">{{ data().totalItems }}</span>\n <span class=\"c2g-ps__count-unit\">Items</span>\n </div>\n\n <!-- Item dots grid -->\n @if (itemDots().length > 0) {\n <div class=\"c2g-ps__dots\">\n @for (done of itemDots(); track $index) {\n <span class=\"c2g-ps__dot\" [class.c2g-ps__dot--done]=\"done\"></span>\n }\n </div>\n }\n\n <!-- Critical alert / all clear -->\n @if (criticalOpen() > 0) {\n <div class=\"c2g-ps__alert\">\n <span class=\"c2g-ps__alert-icon\">\u26A0\uFE0F</span>\n <span class=\"c2g-ps__alert-text\">\n {{ criticalOpen() }} kritische{{ criticalOpen() === 1 ? 's' : '' }} fehlt{{ criticalOpen() === 1 ? '' : 'en' }}\n </span>\n </div>\n } @else if (data().criticalTotal > 0) {\n <div class=\"c2g-ps__all-clear\">\n <span>\u2713 Alle kritischen Items gepackt</span>\n </div>\n }\n\n </div>\n </div>\n\n <!-- Progress bar strip at bottom -->\n <div class=\"c2g-ps__strip-track\">\n <div class=\"c2g-ps__strip-fill\" [style.width.%]=\"percent()\"></div>\n </div>\n\n</div>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-ps{border-radius:var(--c2g-radius-xl, 20px);background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));overflow:hidden;display:flex;flex-direction:column;gap:0;transition:box-shadow .2s ease,transform .2s ease}.c2g-ps:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .12));transform:translateY(-2px)}.c2g-ps--primary{--ps-accent: var(--c2g-theme-primary, #ff6b35);--ps-accent-soft: rgba(255, 107, 53, .12);--ps-glow: rgba(255, 107, 53, .25);--ps-badge-bg: rgba(255, 107, 53, .1);--ps-badge-color: var(--c2g-theme-primary, #ff6b35)}.c2g-ps--success{--ps-accent: var(--c2g-theme-success, #22c55e);--ps-accent-soft: rgba(34, 197, 94, .1);--ps-glow: rgba(34, 197, 94, .3);--ps-badge-bg: rgba(34, 197, 94, .1);--ps-badge-color: var(--c2g-theme-success, #22c55e)}.c2g-ps--warning{--ps-accent: var(--c2g-theme-warning, #f59e0b);--ps-accent-soft: rgba(245, 158, 11, .1);--ps-glow: rgba(245, 158, 11, .3);--ps-badge-bg: rgba(245, 158, 11, .12);--ps-badge-color: var(--c2g-theme-warning, #f59e0b)}.c2g-ps__header{display:flex;align-items:center;justify-content:space-between;padding:16px 18px 0;gap:8px}.c2g-ps__title-row{display:flex;flex-direction:column;gap:1px}.c2g-ps__label{font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-ps__tour{font-size:.8rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c2g-ps__status-badge{font-size:.72rem;font-weight:700;padding:3px 10px;border-radius:20px;background:var(--ps-badge-bg);color:var(--ps-badge-color);white-space:nowrap;flex-shrink:0}.c2g-ps__body{display:flex;align-items:center;gap:16px;padding:14px 18px 16px}.c2g-ps__ring-wrap{position:relative;width:108px;height:108px;flex-shrink:0}.c2g-ps__ring{width:100%;height:100%;filter:drop-shadow(0 0 8px var(--ps-glow, transparent))}.c2g-ps__ring-track{stroke:var(--c2g-theme-outline-variant, rgba(0, 0, 0, .08))}.c2g-ps__ring-fill{stroke:var(--ps-accent);transition:stroke-dashoffset .9s cubic-bezier(.4,0,.2,1)}.c2g-ps__tick{stroke:var(--c2g-theme-outline-variant, rgba(0, 0, 0, .1));stroke-width:1.5;stroke-linecap:round}.c2g-ps__ring-center{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0}.c2g-ps__percent{font-size:1.6rem;font-weight:900;line-height:1;color:var(--ps-accent);letter-spacing:-.02em}.c2g-ps__percent-unit{font-size:.9rem;font-weight:700;opacity:.7}.c2g-ps__ring-emoji{font-size:1rem;line-height:1;margin-top:2px}.c2g-ps__info{flex:1;min-width:0;display:flex;flex-direction:column;gap:10px}.c2g-ps__count-row{display:flex;align-items:baseline;gap:3px}.c2g-ps__count-main{font-size:2rem;font-weight:900;line-height:1;color:var(--ps-accent)}.c2g-ps__count-sep{font-size:1.1rem;font-weight:400;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));margin:0 1px}.c2g-ps__count-total{font-size:1.1rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}.c2g-ps__count-unit{font-size:.75rem;font-weight:500;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));margin-left:3px}.c2g-ps__dots{display:flex;flex-wrap:wrap;gap:4px}.c2g-ps__dot{width:10px;height:10px;border-radius:3px;background:var(--c2g-theme-outline-variant, rgba(0, 0, 0, .1));transition:background .3s ease,transform .2s ease}.c2g-ps__dot--done{background:var(--ps-accent);transform:scale(1.05)}.c2g-ps__dot--done:last-child{box-shadow:0 0 0 2px var(--ps-accent-soft)}.c2g-ps__alert{display:flex;align-items:center;gap:5px;font-size:.78rem;font-weight:700;color:var(--c2g-theme-warning, #f59e0b);background:#f59e0b14;border-radius:8px;padding:5px 8px}.c2g-ps__all-clear{font-size:.78rem;font-weight:700;color:var(--c2g-theme-success, #22c55e);background:#22c55e14;border-radius:8px;padding:5px 8px}.c2g-ps__strip-track{height:4px;background:var(--c2g-theme-outline-variant, rgba(0, 0, 0, .06))}.c2g-ps__strip-fill{height:100%;background:var(--ps-accent);transition:width .9s cubic-bezier(.4,0,.2,1);border-radius:0 2px 2px 0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1685
|
+
}
|
|
1686
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: PackStatusWidgetComponent, decorators: [{
|
|
1687
|
+
type: Component,
|
|
1688
|
+
args: [{ selector: 'c2g-pack-status-widget', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"c2g-ps\" [class]=\"'c2g-ps--' + statusColor()\">\n\n <!-- Header -->\n <div class=\"c2g-ps__header\">\n <div class=\"c2g-ps__title-row\">\n <span class=\"c2g-ps__label\">\uD83C\uDF92 Packstatus</span>\n @if (data().tourName) {\n <span class=\"c2g-ps__tour\">{{ data().tourName }}</span>\n }\n </div>\n <span class=\"c2g-ps__status-badge\">{{ statusLabel() }}</span>\n </div>\n\n <!-- Main: ring + info side by side -->\n <div class=\"c2g-ps__body\">\n\n <!-- SVG Ring -->\n <div class=\"c2g-ps__ring-wrap\">\n <svg class=\"c2g-ps__ring\" viewBox=\"0 0 112 112\" aria-hidden=\"true\">\n <!-- Track -->\n <circle class=\"c2g-ps__ring-track\"\n cx=\"56\" cy=\"56\" [attr.r]=\"radius\"\n fill=\"none\" [attr.stroke-width]=\"strokeWidth\" />\n <!-- Fill -->\n <circle class=\"c2g-ps__ring-fill\"\n cx=\"56\" cy=\"56\" [attr.r]=\"radius\"\n fill=\"none\" [attr.stroke-width]=\"strokeWidth\"\n stroke-linecap=\"round\"\n [attr.stroke-dasharray]=\"circumference\"\n [attr.stroke-dashoffset]=\"strokeDashoffset()\"\n transform=\"rotate(-90 56 56)\" />\n <!-- Tick marks -->\n @for (t of ticks; track $index) {\n <line class=\"c2g-ps__tick\"\n [attr.x1]=\"t.x1\" [attr.y1]=\"t.y1\"\n [attr.x2]=\"t.x2\" [attr.y2]=\"t.y2\" />\n }\n </svg>\n <!-- Center content -->\n <div class=\"c2g-ps__ring-center\">\n <span class=\"c2g-ps__percent\">{{ displayPercent() }}<span class=\"c2g-ps__percent-unit\">%</span></span>\n @if (statusColor() === 'success') {\n <span class=\"c2g-ps__ring-emoji\">\uD83C\uDF89</span>\n }\n </div>\n </div>\n\n <!-- Info panel -->\n <div class=\"c2g-ps__info\">\n\n <!-- Item count -->\n <div class=\"c2g-ps__count-row\">\n <span class=\"c2g-ps__count-main\">{{ data().completedItems }}</span>\n <span class=\"c2g-ps__count-sep\">/</span>\n <span class=\"c2g-ps__count-total\">{{ data().totalItems }}</span>\n <span class=\"c2g-ps__count-unit\">Items</span>\n </div>\n\n <!-- Item dots grid -->\n @if (itemDots().length > 0) {\n <div class=\"c2g-ps__dots\">\n @for (done of itemDots(); track $index) {\n <span class=\"c2g-ps__dot\" [class.c2g-ps__dot--done]=\"done\"></span>\n }\n </div>\n }\n\n <!-- Critical alert / all clear -->\n @if (criticalOpen() > 0) {\n <div class=\"c2g-ps__alert\">\n <span class=\"c2g-ps__alert-icon\">\u26A0\uFE0F</span>\n <span class=\"c2g-ps__alert-text\">\n {{ criticalOpen() }} kritische{{ criticalOpen() === 1 ? 's' : '' }} fehlt{{ criticalOpen() === 1 ? '' : 'en' }}\n </span>\n </div>\n } @else if (data().criticalTotal > 0) {\n <div class=\"c2g-ps__all-clear\">\n <span>\u2713 Alle kritischen Items gepackt</span>\n </div>\n }\n\n </div>\n </div>\n\n <!-- Progress bar strip at bottom -->\n <div class=\"c2g-ps__strip-track\">\n <div class=\"c2g-ps__strip-fill\" [style.width.%]=\"percent()\"></div>\n </div>\n\n</div>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-ps{border-radius:var(--c2g-radius-xl, 20px);background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));overflow:hidden;display:flex;flex-direction:column;gap:0;transition:box-shadow .2s ease,transform .2s ease}.c2g-ps:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .12));transform:translateY(-2px)}.c2g-ps--primary{--ps-accent: var(--c2g-theme-primary, #ff6b35);--ps-accent-soft: rgba(255, 107, 53, .12);--ps-glow: rgba(255, 107, 53, .25);--ps-badge-bg: rgba(255, 107, 53, .1);--ps-badge-color: var(--c2g-theme-primary, #ff6b35)}.c2g-ps--success{--ps-accent: var(--c2g-theme-success, #22c55e);--ps-accent-soft: rgba(34, 197, 94, .1);--ps-glow: rgba(34, 197, 94, .3);--ps-badge-bg: rgba(34, 197, 94, .1);--ps-badge-color: var(--c2g-theme-success, #22c55e)}.c2g-ps--warning{--ps-accent: var(--c2g-theme-warning, #f59e0b);--ps-accent-soft: rgba(245, 158, 11, .1);--ps-glow: rgba(245, 158, 11, .3);--ps-badge-bg: rgba(245, 158, 11, .12);--ps-badge-color: var(--c2g-theme-warning, #f59e0b)}.c2g-ps__header{display:flex;align-items:center;justify-content:space-between;padding:16px 18px 0;gap:8px}.c2g-ps__title-row{display:flex;flex-direction:column;gap:1px}.c2g-ps__label{font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-ps__tour{font-size:.8rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c2g-ps__status-badge{font-size:.72rem;font-weight:700;padding:3px 10px;border-radius:20px;background:var(--ps-badge-bg);color:var(--ps-badge-color);white-space:nowrap;flex-shrink:0}.c2g-ps__body{display:flex;align-items:center;gap:16px;padding:14px 18px 16px}.c2g-ps__ring-wrap{position:relative;width:108px;height:108px;flex-shrink:0}.c2g-ps__ring{width:100%;height:100%;filter:drop-shadow(0 0 8px var(--ps-glow, transparent))}.c2g-ps__ring-track{stroke:var(--c2g-theme-outline-variant, rgba(0, 0, 0, .08))}.c2g-ps__ring-fill{stroke:var(--ps-accent);transition:stroke-dashoffset .9s cubic-bezier(.4,0,.2,1)}.c2g-ps__tick{stroke:var(--c2g-theme-outline-variant, rgba(0, 0, 0, .1));stroke-width:1.5;stroke-linecap:round}.c2g-ps__ring-center{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0}.c2g-ps__percent{font-size:1.6rem;font-weight:900;line-height:1;color:var(--ps-accent);letter-spacing:-.02em}.c2g-ps__percent-unit{font-size:.9rem;font-weight:700;opacity:.7}.c2g-ps__ring-emoji{font-size:1rem;line-height:1;margin-top:2px}.c2g-ps__info{flex:1;min-width:0;display:flex;flex-direction:column;gap:10px}.c2g-ps__count-row{display:flex;align-items:baseline;gap:3px}.c2g-ps__count-main{font-size:2rem;font-weight:900;line-height:1;color:var(--ps-accent)}.c2g-ps__count-sep{font-size:1.1rem;font-weight:400;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));margin:0 1px}.c2g-ps__count-total{font-size:1.1rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}.c2g-ps__count-unit{font-size:.75rem;font-weight:500;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));margin-left:3px}.c2g-ps__dots{display:flex;flex-wrap:wrap;gap:4px}.c2g-ps__dot{width:10px;height:10px;border-radius:3px;background:var(--c2g-theme-outline-variant, rgba(0, 0, 0, .1));transition:background .3s ease,transform .2s ease}.c2g-ps__dot--done{background:var(--ps-accent);transform:scale(1.05)}.c2g-ps__dot--done:last-child{box-shadow:0 0 0 2px var(--ps-accent-soft)}.c2g-ps__alert{display:flex;align-items:center;gap:5px;font-size:.78rem;font-weight:700;color:var(--c2g-theme-warning, #f59e0b);background:#f59e0b14;border-radius:8px;padding:5px 8px}.c2g-ps__all-clear{font-size:.78rem;font-weight:700;color:var(--c2g-theme-success, #22c55e);background:#22c55e14;border-radius:8px;padding:5px 8px}.c2g-ps__strip-track{height:4px;background:var(--c2g-theme-outline-variant, rgba(0, 0, 0, .06))}.c2g-ps__strip-fill{height:100%;background:var(--ps-accent);transition:width .9s cubic-bezier(.4,0,.2,1);border-radius:0 2px 2px 0}\n"] }]
|
|
1689
|
+
}], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }] } });
|
|
1690
|
+
|
|
1691
|
+
const SCALE_MAX_G = 30_000;
|
|
1692
|
+
const ZONES = [
|
|
1693
|
+
{ label: 'Ultraleicht', maxG: 7_000, color: '#22c55e', bgColor: 'rgba(34,197,94,0.18)' },
|
|
1694
|
+
{ label: 'Leicht', maxG: 12_000, color: '#84cc16', bgColor: 'rgba(132,204,22,0.15)' },
|
|
1695
|
+
{ label: 'Mittel', maxG: 18_000, color: '#f59e0b', bgColor: 'rgba(245,158,11,0.14)' },
|
|
1696
|
+
{ label: 'Schwer', maxG: 25_000, color: '#f97316', bgColor: 'rgba(249,115,22,0.14)' },
|
|
1697
|
+
{ label: 'Sehr schwer', maxG: 30_000, color: '#ef4444', bgColor: 'rgba(239,68,68,0.15)' },
|
|
1698
|
+
];
|
|
1699
|
+
class PackWeightWidgetComponent {
|
|
1700
|
+
data = input.required(...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
1701
|
+
zone = computed(() => ZONES.find(z => this.data().currentWeightG <= z.maxG) ?? ZONES[ZONES.length - 1], ...(ngDevMode ? [{ debugName: "zone" }] : []));
|
|
1702
|
+
scalePercent = computed(() => Math.min(100, (this.data().currentWeightG / SCALE_MAX_G) * 100), ...(ngDevMode ? [{ debugName: "scalePercent" }] : []));
|
|
1703
|
+
prevScalePercent = computed(() => {
|
|
1704
|
+
const prev = this.data().previousWeightG;
|
|
1705
|
+
if (prev == null)
|
|
1706
|
+
return null;
|
|
1707
|
+
return Math.min(100, (prev / SCALE_MAX_G) * 100);
|
|
1708
|
+
}, ...(ngDevMode ? [{ debugName: "prevScalePercent" }] : []));
|
|
1709
|
+
diffG = computed(() => {
|
|
1710
|
+
const prev = this.data().previousWeightG;
|
|
1711
|
+
if (prev == null)
|
|
1712
|
+
return null;
|
|
1713
|
+
return this.data().currentWeightG - prev;
|
|
1714
|
+
}, ...(ngDevMode ? [{ debugName: "diffG" }] : []));
|
|
1715
|
+
diffKg = computed(() => {
|
|
1716
|
+
const d = this.diffG();
|
|
1717
|
+
if (d == null)
|
|
1718
|
+
return null;
|
|
1719
|
+
return (Math.abs(d) / 1000).toFixed(1);
|
|
1720
|
+
}, ...(ngDevMode ? [{ debugName: "diffKg" }] : []));
|
|
1721
|
+
diffTrend = computed(() => {
|
|
1722
|
+
const d = this.diffG();
|
|
1723
|
+
if (d == null)
|
|
1724
|
+
return null;
|
|
1725
|
+
if (d < 0)
|
|
1726
|
+
return 'better';
|
|
1727
|
+
if (d > 0)
|
|
1728
|
+
return 'worse';
|
|
1729
|
+
return 'same';
|
|
1730
|
+
}, ...(ngDevMode ? [{ debugName: "diffTrend" }] : []));
|
|
1731
|
+
// Animated values
|
|
1732
|
+
animatedPercent = signal(0, ...(ngDevMode ? [{ debugName: "animatedPercent" }] : []));
|
|
1733
|
+
displayKg = signal('0.0', ...(ngDevMode ? [{ debugName: "displayKg" }] : []));
|
|
1734
|
+
rafMarker = 0;
|
|
1735
|
+
rafKg = 0;
|
|
1736
|
+
constructor() {
|
|
1737
|
+
effect(() => {
|
|
1738
|
+
const targetPct = this.scalePercent();
|
|
1739
|
+
const targetKg = this.data().currentWeightG / 1000;
|
|
1740
|
+
// Marker animation
|
|
1741
|
+
cancelAnimationFrame(this.rafMarker);
|
|
1742
|
+
const fromPct = this.animatedPercent();
|
|
1743
|
+
const startM = performance.now();
|
|
1744
|
+
const tickMarker = (now) => {
|
|
1745
|
+
const t = Math.min((now - startM) / 900, 1);
|
|
1746
|
+
const e = 1 - Math.pow(1 - t, 4);
|
|
1747
|
+
this.animatedPercent.set(fromPct + (targetPct - fromPct) * e);
|
|
1748
|
+
if (t < 1)
|
|
1749
|
+
this.rafMarker = requestAnimationFrame(tickMarker);
|
|
1750
|
+
};
|
|
1751
|
+
this.rafMarker = requestAnimationFrame(tickMarker);
|
|
1752
|
+
// Count-up kg
|
|
1753
|
+
cancelAnimationFrame(this.rafKg);
|
|
1754
|
+
const startK = performance.now();
|
|
1755
|
+
const tickKg = (now) => {
|
|
1756
|
+
const t = Math.min((now - startK) / 900, 1);
|
|
1757
|
+
const e = 1 - Math.pow(1 - t, 3);
|
|
1758
|
+
this.displayKg.set((e * targetKg).toFixed(1));
|
|
1759
|
+
if (t < 1)
|
|
1760
|
+
this.rafKg = requestAnimationFrame(tickKg);
|
|
1761
|
+
};
|
|
1762
|
+
this.rafKg = requestAnimationFrame(tickKg);
|
|
1763
|
+
});
|
|
1764
|
+
}
|
|
1765
|
+
// Zone segments for colour gradient bar
|
|
1766
|
+
zoneSegments = computed(() => {
|
|
1767
|
+
let prev = 0;
|
|
1768
|
+
return ZONES.map(z => {
|
|
1769
|
+
const right = (z.maxG / SCALE_MAX_G) * 100;
|
|
1770
|
+
const seg = { left: prev, width: right - prev, color: z.color, bgColor: z.bgColor, label: z.label };
|
|
1771
|
+
prev = right;
|
|
1772
|
+
return seg;
|
|
1773
|
+
});
|
|
1774
|
+
}, ...(ngDevMode ? [{ debugName: "zoneSegments" }] : []));
|
|
1775
|
+
// Tick marks: 0 5 10 15 20 25 30 kg
|
|
1776
|
+
ticks = Array.from({ length: 7 }, (_, i) => ({
|
|
1777
|
+
pct: (i * 5_000 / SCALE_MAX_G) * 100,
|
|
1778
|
+
label: `${i * 5}`,
|
|
1779
|
+
}));
|
|
1780
|
+
ngOnDestroy() {
|
|
1781
|
+
cancelAnimationFrame(this.rafMarker);
|
|
1782
|
+
cancelAnimationFrame(this.rafKg);
|
|
1783
|
+
}
|
|
1784
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: PackWeightWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1785
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: PackWeightWidgetComponent, isStandalone: true, selector: "c2g-pack-weight-widget", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"c2g-pw\">\n\n <!-- Header -->\n <div class=\"c2g-pw__header\">\n <div class=\"c2g-pw__title-col\">\n <span class=\"c2g-pw__label\">Packgewicht</span>\n @if (data().tourName) {\n <span class=\"c2g-pw__tour\">{{ data().tourName }}</span>\n }\n </div>\n <!-- Zone badge -->\n <span class=\"c2g-pw__zone-badge\"\n [style.background]=\"zone().bgColor\"\n [style.color]=\"zone().color\">\n {{ zone().label }}\n </span>\n </div>\n\n <!-- Big value -->\n <div class=\"c2g-pw__value-row\">\n <span class=\"c2g-pw__value\" [style.color]=\"zone().color\">{{ displayKg() }}</span>\n <span class=\"c2g-pw__unit\">kg</span>\n\n <!-- Diff pill -->\n @if (diffG() != null) {\n <span class=\"c2g-pw__diff-pill\"\n [class.c2g-pw__diff-pill--better]=\"diffTrend() === 'better'\"\n [class.c2g-pw__diff-pill--worse]=\"diffTrend() === 'worse'\">\n @if (diffTrend() === 'better') { \u2193 {{ diffKg() }} kg }\n @else if (diffTrend() === 'worse') { \u2191 {{ diffKg() }} kg }\n @else { \u2192 gleich }\n </span>\n }\n </div>\n\n <!-- Scale bar -->\n <div class=\"c2g-pw__scale\">\n\n <!-- Colour zone segments -->\n <div class=\"c2g-pw__zones\">\n @for (seg of zoneSegments(); track seg.label) {\n <div class=\"c2g-pw__zone-seg\"\n [style.width.%]=\"seg.width\"\n [style.background]=\"seg.bgColor\"\n [style.border-right]=\"'1px solid ' + seg.color + '22'\">\n </div>\n }\n </div>\n\n <!-- Gradient fill up to current value -->\n <div class=\"c2g-pw__fill-track\">\n <div class=\"c2g-pw__fill\"\n [style.width.%]=\"animatedPercent()\"\n [style.background]=\"'linear-gradient(90deg, #22c55e, ' + zone().color + ')'\">\n </div>\n </div>\n\n <!-- Previous marker -->\n @if (prevScalePercent() != null) {\n <div class=\"c2g-pw__marker c2g-pw__marker--prev\"\n [style.left.%]=\"prevScalePercent()\">\n <div class=\"c2g-pw__marker-line\"></div>\n <span class=\"c2g-pw__marker-label\">Letzte Tour</span>\n </div>\n }\n\n <!-- Current marker -->\n <div class=\"c2g-pw__marker c2g-pw__marker--current\"\n [style.left.%]=\"animatedPercent()\"\n [style.--marker-color]=\"zone().color\">\n <div class=\"c2g-pw__marker-pin\"></div>\n <div class=\"c2g-pw__marker-line\"></div>\n </div>\n\n <!-- Tick marks -->\n <div class=\"c2g-pw__ticks\">\n @for (tick of ticks; track tick.pct) {\n <div class=\"c2g-pw__tick\" [style.left.%]=\"tick.pct\">\n <div class=\"c2g-pw__tick-line\"></div>\n <span class=\"c2g-pw__tick-label\">{{ tick.label }}</span>\n </div>\n }\n </div>\n\n </div>\n\n <!-- Zone legend -->\n <div class=\"c2g-pw__legend\">\n @for (seg of zoneSegments(); track seg.label) {\n <div class=\"c2g-pw__legend-item\"\n [class.c2g-pw__legend-item--active]=\"seg.label === zone().label\">\n <span class=\"c2g-pw__legend-dot\" [style.background]=\"seg.color\"></span>\n <span class=\"c2g-pw__legend-label\">{{ seg.label }}</span>\n </div>\n }\n </div>\n\n</div>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-pw{border-radius:var(--c2g-radius-xl, 20px);background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));padding:20px 20px 16px;display:flex;flex-direction:column;gap:12px;transition:box-shadow .2s ease,transform .2s ease}.c2g-pw:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .11));transform:translateY(-2px)}.c2g-pw__header{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}.c2g-pw__title-col{display:flex;flex-direction:column;gap:2px}.c2g-pw__label{font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-pw__tour{font-size:.82rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c2g-pw__zone-badge{font-size:.72rem;font-weight:800;padding:3px 10px;border-radius:20px;white-space:nowrap;flex-shrink:0}.c2g-pw__value-row{display:flex;align-items:baseline;gap:5px}.c2g-pw__value{font-size:3.2rem;font-weight:900;line-height:1;letter-spacing:-.03em;transition:color .4s ease}.c2g-pw__unit{font-size:1.3rem;font-weight:600;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));margin-bottom:3px}.c2g-pw__diff-pill{margin-left:8px;font-size:.78rem;font-weight:700;padding:3px 9px;border-radius:20px;background:var(--c2g-theme-surface-container, rgba(0, 0, 0, .05));color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));align-self:center;white-space:nowrap}.c2g-pw__diff-pill--better{background:#22c55e1f;color:#22c55e}.c2g-pw__diff-pill--worse{background:#f973161f;color:#f97316}.c2g-pw__scale{position:relative;height:52px;margin:4px 0 8px}.c2g-pw__zones{position:absolute;top:0;left:0;right:0;height:12px;border-radius:6px;overflow:hidden;display:flex}.c2g-pw__zone-seg{height:100%}.c2g-pw__fill-track{position:absolute;top:0;left:0;right:0;height:12px;border-radius:6px;overflow:hidden}.c2g-pw__fill{height:100%;border-radius:6px;transition:width .9s cubic-bezier(.4,0,.2,1);opacity:.85}.c2g-pw__marker{position:absolute;top:0;transform:translate(-50%);display:flex;flex-direction:column;align-items:center;pointer-events:none}.c2g-pw__marker--current{transition:left .9s cubic-bezier(.4,0,.2,1);z-index:2}.c2g-pw__marker--prev{z-index:1;opacity:.5}.c2g-pw__marker-pin{width:14px;height:14px;border-radius:50%;border:2.5px solid #fff;background:var(--marker-color, #ff6b35);box-shadow:0 0 0 2px var(--marker-color, #ff6b35),0 2px 8px #0003;margin-top:-1px;position:relative;z-index:1}.c2g-pw__marker-line{width:1.5px;height:10px;background:currentColor;opacity:.4;margin-top:1px}.c2g-pw__marker-label{font-size:.58rem;font-weight:700;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));white-space:nowrap;margin-top:1px;text-transform:uppercase;letter-spacing:.04em}.c2g-pw__ticks{position:absolute;top:14px;left:0;right:0}.c2g-pw__tick{position:absolute;transform:translate(-50%);display:flex;flex-direction:column;align-items:center;gap:1px}.c2g-pw__tick:first-child{transform:translate(0)}.c2g-pw__tick:last-child{transform:translate(-100%)}.c2g-pw__tick-line{width:1px;height:5px;background:var(--c2g-theme-outline-variant, rgba(0, 0, 0, .15))}.c2g-pw__tick-label{font-size:.6rem;font-weight:600;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));white-space:nowrap}.c2g-pw__legend{display:flex;flex-wrap:wrap;gap:6px 10px;padding-top:2px}.c2g-pw__legend-item{display:flex;align-items:center;gap:4px;opacity:.45;transition:opacity .2s ease}.c2g-pw__legend-item--active{opacity:1}.c2g-pw__legend-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0}.c2g-pw__legend-label{font-size:.7rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1786
|
+
}
|
|
1787
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: PackWeightWidgetComponent, decorators: [{
|
|
1788
|
+
type: Component,
|
|
1789
|
+
args: [{ selector: 'c2g-pack-weight-widget', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"c2g-pw\">\n\n <!-- Header -->\n <div class=\"c2g-pw__header\">\n <div class=\"c2g-pw__title-col\">\n <span class=\"c2g-pw__label\">Packgewicht</span>\n @if (data().tourName) {\n <span class=\"c2g-pw__tour\">{{ data().tourName }}</span>\n }\n </div>\n <!-- Zone badge -->\n <span class=\"c2g-pw__zone-badge\"\n [style.background]=\"zone().bgColor\"\n [style.color]=\"zone().color\">\n {{ zone().label }}\n </span>\n </div>\n\n <!-- Big value -->\n <div class=\"c2g-pw__value-row\">\n <span class=\"c2g-pw__value\" [style.color]=\"zone().color\">{{ displayKg() }}</span>\n <span class=\"c2g-pw__unit\">kg</span>\n\n <!-- Diff pill -->\n @if (diffG() != null) {\n <span class=\"c2g-pw__diff-pill\"\n [class.c2g-pw__diff-pill--better]=\"diffTrend() === 'better'\"\n [class.c2g-pw__diff-pill--worse]=\"diffTrend() === 'worse'\">\n @if (diffTrend() === 'better') { \u2193 {{ diffKg() }} kg }\n @else if (diffTrend() === 'worse') { \u2191 {{ diffKg() }} kg }\n @else { \u2192 gleich }\n </span>\n }\n </div>\n\n <!-- Scale bar -->\n <div class=\"c2g-pw__scale\">\n\n <!-- Colour zone segments -->\n <div class=\"c2g-pw__zones\">\n @for (seg of zoneSegments(); track seg.label) {\n <div class=\"c2g-pw__zone-seg\"\n [style.width.%]=\"seg.width\"\n [style.background]=\"seg.bgColor\"\n [style.border-right]=\"'1px solid ' + seg.color + '22'\">\n </div>\n }\n </div>\n\n <!-- Gradient fill up to current value -->\n <div class=\"c2g-pw__fill-track\">\n <div class=\"c2g-pw__fill\"\n [style.width.%]=\"animatedPercent()\"\n [style.background]=\"'linear-gradient(90deg, #22c55e, ' + zone().color + ')'\">\n </div>\n </div>\n\n <!-- Previous marker -->\n @if (prevScalePercent() != null) {\n <div class=\"c2g-pw__marker c2g-pw__marker--prev\"\n [style.left.%]=\"prevScalePercent()\">\n <div class=\"c2g-pw__marker-line\"></div>\n <span class=\"c2g-pw__marker-label\">Letzte Tour</span>\n </div>\n }\n\n <!-- Current marker -->\n <div class=\"c2g-pw__marker c2g-pw__marker--current\"\n [style.left.%]=\"animatedPercent()\"\n [style.--marker-color]=\"zone().color\">\n <div class=\"c2g-pw__marker-pin\"></div>\n <div class=\"c2g-pw__marker-line\"></div>\n </div>\n\n <!-- Tick marks -->\n <div class=\"c2g-pw__ticks\">\n @for (tick of ticks; track tick.pct) {\n <div class=\"c2g-pw__tick\" [style.left.%]=\"tick.pct\">\n <div class=\"c2g-pw__tick-line\"></div>\n <span class=\"c2g-pw__tick-label\">{{ tick.label }}</span>\n </div>\n }\n </div>\n\n </div>\n\n <!-- Zone legend -->\n <div class=\"c2g-pw__legend\">\n @for (seg of zoneSegments(); track seg.label) {\n <div class=\"c2g-pw__legend-item\"\n [class.c2g-pw__legend-item--active]=\"seg.label === zone().label\">\n <span class=\"c2g-pw__legend-dot\" [style.background]=\"seg.color\"></span>\n <span class=\"c2g-pw__legend-label\">{{ seg.label }}</span>\n </div>\n }\n </div>\n\n</div>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-pw{border-radius:var(--c2g-radius-xl, 20px);background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));padding:20px 20px 16px;display:flex;flex-direction:column;gap:12px;transition:box-shadow .2s ease,transform .2s ease}.c2g-pw:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .11));transform:translateY(-2px)}.c2g-pw__header{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}.c2g-pw__title-col{display:flex;flex-direction:column;gap:2px}.c2g-pw__label{font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-pw__tour{font-size:.82rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c2g-pw__zone-badge{font-size:.72rem;font-weight:800;padding:3px 10px;border-radius:20px;white-space:nowrap;flex-shrink:0}.c2g-pw__value-row{display:flex;align-items:baseline;gap:5px}.c2g-pw__value{font-size:3.2rem;font-weight:900;line-height:1;letter-spacing:-.03em;transition:color .4s ease}.c2g-pw__unit{font-size:1.3rem;font-weight:600;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));margin-bottom:3px}.c2g-pw__diff-pill{margin-left:8px;font-size:.78rem;font-weight:700;padding:3px 9px;border-radius:20px;background:var(--c2g-theme-surface-container, rgba(0, 0, 0, .05));color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));align-self:center;white-space:nowrap}.c2g-pw__diff-pill--better{background:#22c55e1f;color:#22c55e}.c2g-pw__diff-pill--worse{background:#f973161f;color:#f97316}.c2g-pw__scale{position:relative;height:52px;margin:4px 0 8px}.c2g-pw__zones{position:absolute;top:0;left:0;right:0;height:12px;border-radius:6px;overflow:hidden;display:flex}.c2g-pw__zone-seg{height:100%}.c2g-pw__fill-track{position:absolute;top:0;left:0;right:0;height:12px;border-radius:6px;overflow:hidden}.c2g-pw__fill{height:100%;border-radius:6px;transition:width .9s cubic-bezier(.4,0,.2,1);opacity:.85}.c2g-pw__marker{position:absolute;top:0;transform:translate(-50%);display:flex;flex-direction:column;align-items:center;pointer-events:none}.c2g-pw__marker--current{transition:left .9s cubic-bezier(.4,0,.2,1);z-index:2}.c2g-pw__marker--prev{z-index:1;opacity:.5}.c2g-pw__marker-pin{width:14px;height:14px;border-radius:50%;border:2.5px solid #fff;background:var(--marker-color, #ff6b35);box-shadow:0 0 0 2px var(--marker-color, #ff6b35),0 2px 8px #0003;margin-top:-1px;position:relative;z-index:1}.c2g-pw__marker-line{width:1.5px;height:10px;background:currentColor;opacity:.4;margin-top:1px}.c2g-pw__marker-label{font-size:.58rem;font-weight:700;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));white-space:nowrap;margin-top:1px;text-transform:uppercase;letter-spacing:.04em}.c2g-pw__ticks{position:absolute;top:14px;left:0;right:0}.c2g-pw__tick{position:absolute;transform:translate(-50%);display:flex;flex-direction:column;align-items:center;gap:1px}.c2g-pw__tick:first-child{transform:translate(0)}.c2g-pw__tick:last-child{transform:translate(-100%)}.c2g-pw__tick-line{width:1px;height:5px;background:var(--c2g-theme-outline-variant, rgba(0, 0, 0, .15))}.c2g-pw__tick-label{font-size:.6rem;font-weight:600;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));white-space:nowrap}.c2g-pw__legend{display:flex;flex-wrap:wrap;gap:6px 10px;padding-top:2px}.c2g-pw__legend-item{display:flex;align-items:center;gap:4px;opacity:.45;transition:opacity .2s ease}.c2g-pw__legend-item--active{opacity:1}.c2g-pw__legend-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0}.c2g-pw__legend-label{font-size:.7rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}\n"] }]
|
|
1790
|
+
}], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }] } });
|
|
1791
|
+
|
|
1792
|
+
class MemberReadinessWidgetComponent {
|
|
1793
|
+
members = input.required(...(ngDevMode ? [{ debugName: "members" }] : []));
|
|
1794
|
+
tourName = input(undefined, ...(ngDevMode ? [{ debugName: "tourName" }] : []));
|
|
1795
|
+
overallPercent = computed(() => {
|
|
1796
|
+
const m = this.members();
|
|
1797
|
+
if (!m.length)
|
|
1798
|
+
return 0;
|
|
1799
|
+
const total = m.reduce((s, e) => s + e.totalItems, 0);
|
|
1800
|
+
if (total === 0)
|
|
1801
|
+
return 0;
|
|
1802
|
+
return Math.round((m.reduce((s, e) => s + e.completedItems, 0) / total) * 100);
|
|
1803
|
+
}, ...(ngDevMode ? [{ debugName: "overallPercent" }] : []));
|
|
1804
|
+
fullyReady = computed(() => this.members().filter(m => this.readyPercent(m) === 100).length, ...(ngDevMode ? [{ debugName: "fullyReady" }] : []));
|
|
1805
|
+
// Groups only shown when >= 6 members, otherwise flat list
|
|
1806
|
+
useGrouped = computed(() => this.members().length >= 6, ...(ngDevMode ? [{ debugName: "useGrouped" }] : []));
|
|
1807
|
+
groups = computed(() => {
|
|
1808
|
+
const buckets = [
|
|
1809
|
+
{ label: 'Bereit', emoji: '✅', color: 'success', min: 100, max: 100, members: [] },
|
|
1810
|
+
{ label: 'Fast fertig', emoji: '🔜', color: 'warning', min: 75, max: 99, members: [] },
|
|
1811
|
+
{ label: 'Unterwegs', emoji: '📦', color: 'warning', min: 40, max: 74, members: [] },
|
|
1812
|
+
{ label: 'Kaum gepackt', emoji: '😴', color: 'danger', min: 1, max: 39, members: [] },
|
|
1813
|
+
{ label: 'Nicht gestartet', emoji: '❌', color: 'neutral', min: 0, max: 0, members: [] },
|
|
1814
|
+
];
|
|
1815
|
+
for (const m of this.members()) {
|
|
1816
|
+
const p = this.readyPercent(m);
|
|
1817
|
+
const bucket = buckets.find(b => p >= b.min && p <= b.max) ?? buckets[buckets.length - 1];
|
|
1818
|
+
bucket.members.push(m);
|
|
1819
|
+
}
|
|
1820
|
+
return buckets.filter(b => b.members.length > 0);
|
|
1821
|
+
}, ...(ngDevMode ? [{ debugName: "groups" }] : []));
|
|
1822
|
+
flatSorted = computed(() => [...this.members()].sort((a, b) => this.readyPercent(b) - this.readyPercent(a)), ...(ngDevMode ? [{ debugName: "flatSorted" }] : []));
|
|
1823
|
+
readyPercent(entry) {
|
|
1824
|
+
if (entry.totalItems === 0)
|
|
1825
|
+
return 100;
|
|
1826
|
+
return Math.round((entry.completedItems / entry.totalItems) * 100);
|
|
1827
|
+
}
|
|
1828
|
+
readyColor(entry) {
|
|
1829
|
+
const p = this.readyPercent(entry);
|
|
1830
|
+
if (p === 100)
|
|
1831
|
+
return 'success';
|
|
1832
|
+
if (p >= 75)
|
|
1833
|
+
return 'warning';
|
|
1834
|
+
if (p >= 40)
|
|
1835
|
+
return 'warning';
|
|
1836
|
+
if (p > 0)
|
|
1837
|
+
return 'danger';
|
|
1838
|
+
return 'neutral';
|
|
1839
|
+
}
|
|
1840
|
+
// Collapse state per group index
|
|
1841
|
+
collapsed = new Map();
|
|
1842
|
+
toggleGroup(index) {
|
|
1843
|
+
this.collapsed.set(index, !this.collapsed.get(index));
|
|
1844
|
+
}
|
|
1845
|
+
isCollapsed(index) {
|
|
1846
|
+
// Collapse groups with only-ready members by default if many groups
|
|
1847
|
+
return this.collapsed.get(index) ?? false;
|
|
1848
|
+
}
|
|
1849
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MemberReadinessWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1850
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: MemberReadinessWidgetComponent, isStandalone: true, selector: "c2g-member-readiness-widget", inputs: { members: { classPropertyName: "members", publicName: "members", isSignal: true, isRequired: true, transformFunction: null }, tourName: { classPropertyName: "tourName", publicName: "tourName", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"c2g-mr\">\n\n <!-- Header -->\n <div class=\"c2g-mr__header\">\n <div class=\"c2g-mr__title-col\">\n <span class=\"c2g-mr__label\">\uD83D\uDC65 Gruppenbereitschaft</span>\n @if (tourName()) {\n <span class=\"c2g-mr__tour\">{{ tourName() }}</span>\n }\n </div>\n <div class=\"c2g-mr__summary-pill\">\n <span class=\"c2g-mr__summary-ready\">{{ fullyReady() }}</span>\n <span class=\"c2g-mr__summary-sep\">/</span>\n <span class=\"c2g-mr__summary-total\">{{ members().length }}</span>\n <span class=\"c2g-mr__summary-unit\">bereit</span>\n </div>\n </div>\n\n <!-- Overall progress bar -->\n <div class=\"c2g-mr__overall-track\" [attr.aria-valuenow]=\"overallPercent()\" aria-valuemin=\"0\" aria-valuemax=\"100\">\n <div class=\"c2g-mr__overall-fill\"\n [style.width.%]=\"overallPercent()\"\n [class.c2g-mr__overall-fill--success]=\"overallPercent() === 100\">\n </div>\n <span class=\"c2g-mr__overall-pct\">{{ overallPercent() }}%</span>\n </div>\n\n <!-- GROUPED layout (6+ members) -->\n @if (useGrouped()) {\n <div class=\"c2g-mr__groups\">\n @for (group of groups(); track group.label; let gi = $index) {\n <div class=\"c2g-mr__group\" [class]=\"'c2g-mr__group--' + group.color\">\n\n <!-- Group header (clickable to toggle) -->\n <button class=\"c2g-mr__group-header\" (click)=\"toggleGroup(gi)\" type=\"button\">\n <span class=\"c2g-mr__group-emoji\">{{ group.emoji }}</span>\n <span class=\"c2g-mr__group-name\">{{ group.label }}</span>\n <span class=\"c2g-mr__group-count\">{{ group.members.length }}</span>\n <!-- Avatar stack preview (max 4) -->\n <div class=\"c2g-mr__avatar-stack\">\n @for (m of group.members.slice(0, 4); track m.name) {\n <span class=\"c2g-mr__avatar-tiny\" [class]=\"'c2g-mr__avatar-tiny--' + group.color\"\n [title]=\"m.name\">{{ m.initials }}</span>\n }\n @if (group.members.length > 4) {\n <span class=\"c2g-mr__avatar-tiny c2g-mr__avatar-tiny--more\">+{{ group.members.length - 4 }}</span>\n }\n </div>\n <span class=\"c2g-mr__group-chevron\" [class.c2g-mr__group-chevron--open]=\"!isCollapsed(gi)\">\u203A</span>\n </button>\n\n <!-- Member rows (collapsible) -->\n @if (!isCollapsed(gi)) {\n <ul class=\"c2g-mr__member-list\">\n @for (m of group.members; track m.name) {\n <li class=\"c2g-mr__member\" [class.c2g-mr__member--self]=\"m.isSelf\">\n <span class=\"c2g-mr__avatar-sm\" [class]=\"'c2g-mr__avatar-sm--' + group.color\">\n {{ m.initials }}\n </span>\n <span class=\"c2g-mr__member-name\">\n {{ m.name }}\n @if (m.isSelf) { <span class=\"c2g-mr__self-tag\">Du</span> }\n </span>\n <div class=\"c2g-mr__mini-track\">\n <div class=\"c2g-mr__mini-fill\"\n [class]=\"'c2g-mr__mini-fill--' + group.color\"\n [style.width.%]=\"readyPercent(m)\">\n </div>\n </div>\n <span class=\"c2g-mr__member-pct\" [class]=\"'c2g-mr__member-pct--' + group.color\">\n {{ readyPercent(m) }}%\n </span>\n </li>\n }\n </ul>\n }\n </div>\n }\n </div>\n\n } @else {\n <!-- FLAT layout (< 6 members) -->\n <ul class=\"c2g-mr__flat-list\">\n @for (m of flatSorted(); track m.name) {\n <li class=\"c2g-mr__member\" [class.c2g-mr__member--self]=\"m.isSelf\">\n <span class=\"c2g-mr__avatar-sm\" [class]=\"'c2g-mr__avatar-sm--' + readyColor(m)\">\n {{ m.initials }}\n </span>\n <span class=\"c2g-mr__member-name\">\n {{ m.name }}\n @if (m.isSelf) { <span class=\"c2g-mr__self-tag\">Du</span> }\n </span>\n <div class=\"c2g-mr__mini-track\">\n <div class=\"c2g-mr__mini-fill\"\n [class]=\"'c2g-mr__mini-fill--' + readyColor(m)\"\n [style.width.%]=\"readyPercent(m)\">\n </div>\n </div>\n <span class=\"c2g-mr__member-pct\" [class]=\"'c2g-mr__member-pct--' + readyColor(m)\">\n {{ readyPercent(m) }}%\n </span>\n </li>\n }\n </ul>\n }\n\n</div>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-mr{border-radius:var(--c2g-radius-xl, 20px);background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));overflow:hidden;transition:box-shadow .2s ease,transform .2s ease}.c2g-mr:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .12));transform:translateY(-2px)}.c2g-mr__header{display:flex;align-items:center;justify-content:space-between;padding:16px 18px 10px;gap:10px}.c2g-mr__title-col{display:flex;flex-direction:column;gap:2px;min-width:0}.c2g-mr__label{font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-mr__tour{font-size:.85rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c2g-mr__summary-pill{display:flex;align-items:baseline;gap:3px;background:var(--c2g-theme-surface-container, rgba(0, 0, 0, .04));border-radius:20px;padding:4px 12px;flex-shrink:0}.c2g-mr__summary-ready{font-size:1.1rem;font-weight:900;color:var(--c2g-theme-primary, #ff6b35);line-height:1}.c2g-mr__summary-sep{font-size:.8rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-mr__summary-total{font-size:.9rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}.c2g-mr__summary-unit{font-size:.7rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));margin-left:2px}.c2g-mr__overall-track{position:relative;margin:0 18px 12px;height:7px;border-radius:4px;background:var(--c2g-theme-outline-variant, rgba(0, 0, 0, .08));overflow:visible}.c2g-mr__overall-fill{height:100%;border-radius:4px;background:var(--c2g-theme-primary, #ff6b35);transition:width .7s cubic-bezier(.4,0,.2,1)}.c2g-mr__overall-fill--success{background:var(--c2g-theme-success, #22c55e)}.c2g-mr__overall-pct{position:absolute;right:0;top:-18px;font-size:.7rem;font-weight:700;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-mr__groups{display:flex;flex-direction:column;gap:2px;padding:0 10px 12px}.c2g-mr__group{border-radius:12px;overflow:hidden;transition:background .15s ease;background:var(--mr-group-bg)}.c2g-mr__group--success{--mr-accent: #22c55e;--mr-accent-bg: rgba(34, 197, 94, .12);--mr-group-bg: rgba(34, 197, 94, .08)}.c2g-mr__group--warning{--mr-accent: #f59e0b;--mr-accent-bg: rgba(245, 158, 11, .15);--mr-group-bg: rgba(245, 158, 11, .06)}.c2g-mr__group--danger{--mr-accent: #ef4444;--mr-accent-bg: rgba(239, 68, 68, .15);--mr-group-bg: rgba(239, 68, 68, .06)}.c2g-mr__group--neutral{--mr-accent: #9ca3af;--mr-accent-bg: rgba(156, 163, 175, .15);--mr-group-bg: rgba(156, 163, 175, .05)}.c2g-mr__group-header{width:100%;display:flex;align-items:center;gap:6px;padding:8px 10px;background:transparent;border:none;cursor:pointer;text-align:left;border-radius:12px;transition:background .15s ease}.c2g-mr__group-header:hover{background:var(--mr-accent-bg)}.c2g-mr__group-emoji{font-size:1rem;line-height:1;flex-shrink:0}.c2g-mr__group-name{font-size:.8rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));flex:1}.c2g-mr__group-count{font-size:.72rem;font-weight:800;min-width:18px;height:18px;border-radius:9px;background:var(--mr-accent-bg);color:var(--mr-accent);display:flex;align-items:center;justify-content:center;padding:0 5px}.c2g-mr__avatar-stack{display:flex;margin-left:4px}.c2g-mr__avatar-tiny{width:22px;height:22px;border-radius:50%;font-size:.55rem;font-weight:800;display:flex;align-items:center;justify-content:center;margin-left:-6px;border:2px solid var(--c2g-theme-surface, #fff);background:var(--mr-accent-bg);color:var(--mr-accent)}.c2g-mr__avatar-tiny:first-child{margin-left:0}.c2g-mr__avatar-tiny--more{background:var(--c2g-theme-outline-variant, rgba(0, 0, 0, .1));color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));font-size:.5rem}.c2g-mr__group-chevron{font-size:1.1rem;font-weight:700;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));transform:rotate(90deg);transition:transform .2s ease;line-height:1;margin-left:2px}.c2g-mr__group-chevron--open{transform:rotate(-90deg)}.c2g-mr__member-list,.c2g-mr__flat-list{list-style:none;margin:0;padding:0 10px 4px;display:flex;flex-direction:column;gap:2px}.c2g-mr__flat-list{padding:0 10px 14px}.c2g-mr__member{display:flex;align-items:center;gap:8px;padding:5px 6px;border-radius:8px;transition:background .15s ease}.c2g-mr__member:hover{background:var(--c2g-theme-surface-container, rgba(0, 0, 0, .03))}.c2g-mr__member--self{background:#ff6b350f}.c2g-mr__member--self:hover{background:#ff6b351a}.c2g-mr__avatar-sm{width:28px;height:28px;border-radius:50%;font-size:.65rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}.c2g-mr__avatar-sm--success{background:#22c55e1f;color:#22c55e}.c2g-mr__avatar-sm--warning{background:#f59e0b26;color:#f59e0b}.c2g-mr__avatar-sm--danger{background:#ef444426;color:#ef4444}.c2g-mr__avatar-sm--neutral{background:#9ca3af26;color:#9ca3af}.c2g-mr__member-name{font-size:.8rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:flex;align-items:center;gap:4px}.c2g-mr__self-tag{font-size:.6rem;font-weight:800;text-transform:uppercase;padding:1px 5px;border-radius:4px;background:var(--c2g-theme-primary, #ff6b35);color:#fff;flex-shrink:0}.c2g-mr__mini-track{width:52px;height:4px;border-radius:2px;background:var(--c2g-theme-outline-variant, rgba(0, 0, 0, .08));overflow:hidden;flex-shrink:0}.c2g-mr__mini-fill{height:100%;border-radius:2px;transition:width .6s cubic-bezier(.4,0,.2,1)}.c2g-mr__mini-fill--success{background:#22c55e}.c2g-mr__mini-fill--warning{background:#f59e0b}.c2g-mr__mini-fill--danger{background:#ef4444}.c2g-mr__mini-fill--neutral{background:#9ca3af}.c2g-mr__member-pct{font-size:.72rem;font-weight:700;min-width:32px;text-align:right;flex-shrink:0}.c2g-mr__member-pct--success{color:#22c55e}.c2g-mr__member-pct--warning{color:#f59e0b}.c2g-mr__member-pct--danger{color:#ef4444}.c2g-mr__member-pct--neutral{color:#9ca3af}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1851
|
+
}
|
|
1852
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MemberReadinessWidgetComponent, decorators: [{
|
|
1853
|
+
type: Component,
|
|
1854
|
+
args: [{ selector: 'c2g-member-readiness-widget', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"c2g-mr\">\n\n <!-- Header -->\n <div class=\"c2g-mr__header\">\n <div class=\"c2g-mr__title-col\">\n <span class=\"c2g-mr__label\">\uD83D\uDC65 Gruppenbereitschaft</span>\n @if (tourName()) {\n <span class=\"c2g-mr__tour\">{{ tourName() }}</span>\n }\n </div>\n <div class=\"c2g-mr__summary-pill\">\n <span class=\"c2g-mr__summary-ready\">{{ fullyReady() }}</span>\n <span class=\"c2g-mr__summary-sep\">/</span>\n <span class=\"c2g-mr__summary-total\">{{ members().length }}</span>\n <span class=\"c2g-mr__summary-unit\">bereit</span>\n </div>\n </div>\n\n <!-- Overall progress bar -->\n <div class=\"c2g-mr__overall-track\" [attr.aria-valuenow]=\"overallPercent()\" aria-valuemin=\"0\" aria-valuemax=\"100\">\n <div class=\"c2g-mr__overall-fill\"\n [style.width.%]=\"overallPercent()\"\n [class.c2g-mr__overall-fill--success]=\"overallPercent() === 100\">\n </div>\n <span class=\"c2g-mr__overall-pct\">{{ overallPercent() }}%</span>\n </div>\n\n <!-- GROUPED layout (6+ members) -->\n @if (useGrouped()) {\n <div class=\"c2g-mr__groups\">\n @for (group of groups(); track group.label; let gi = $index) {\n <div class=\"c2g-mr__group\" [class]=\"'c2g-mr__group--' + group.color\">\n\n <!-- Group header (clickable to toggle) -->\n <button class=\"c2g-mr__group-header\" (click)=\"toggleGroup(gi)\" type=\"button\">\n <span class=\"c2g-mr__group-emoji\">{{ group.emoji }}</span>\n <span class=\"c2g-mr__group-name\">{{ group.label }}</span>\n <span class=\"c2g-mr__group-count\">{{ group.members.length }}</span>\n <!-- Avatar stack preview (max 4) -->\n <div class=\"c2g-mr__avatar-stack\">\n @for (m of group.members.slice(0, 4); track m.name) {\n <span class=\"c2g-mr__avatar-tiny\" [class]=\"'c2g-mr__avatar-tiny--' + group.color\"\n [title]=\"m.name\">{{ m.initials }}</span>\n }\n @if (group.members.length > 4) {\n <span class=\"c2g-mr__avatar-tiny c2g-mr__avatar-tiny--more\">+{{ group.members.length - 4 }}</span>\n }\n </div>\n <span class=\"c2g-mr__group-chevron\" [class.c2g-mr__group-chevron--open]=\"!isCollapsed(gi)\">\u203A</span>\n </button>\n\n <!-- Member rows (collapsible) -->\n @if (!isCollapsed(gi)) {\n <ul class=\"c2g-mr__member-list\">\n @for (m of group.members; track m.name) {\n <li class=\"c2g-mr__member\" [class.c2g-mr__member--self]=\"m.isSelf\">\n <span class=\"c2g-mr__avatar-sm\" [class]=\"'c2g-mr__avatar-sm--' + group.color\">\n {{ m.initials }}\n </span>\n <span class=\"c2g-mr__member-name\">\n {{ m.name }}\n @if (m.isSelf) { <span class=\"c2g-mr__self-tag\">Du</span> }\n </span>\n <div class=\"c2g-mr__mini-track\">\n <div class=\"c2g-mr__mini-fill\"\n [class]=\"'c2g-mr__mini-fill--' + group.color\"\n [style.width.%]=\"readyPercent(m)\">\n </div>\n </div>\n <span class=\"c2g-mr__member-pct\" [class]=\"'c2g-mr__member-pct--' + group.color\">\n {{ readyPercent(m) }}%\n </span>\n </li>\n }\n </ul>\n }\n </div>\n }\n </div>\n\n } @else {\n <!-- FLAT layout (< 6 members) -->\n <ul class=\"c2g-mr__flat-list\">\n @for (m of flatSorted(); track m.name) {\n <li class=\"c2g-mr__member\" [class.c2g-mr__member--self]=\"m.isSelf\">\n <span class=\"c2g-mr__avatar-sm\" [class]=\"'c2g-mr__avatar-sm--' + readyColor(m)\">\n {{ m.initials }}\n </span>\n <span class=\"c2g-mr__member-name\">\n {{ m.name }}\n @if (m.isSelf) { <span class=\"c2g-mr__self-tag\">Du</span> }\n </span>\n <div class=\"c2g-mr__mini-track\">\n <div class=\"c2g-mr__mini-fill\"\n [class]=\"'c2g-mr__mini-fill--' + readyColor(m)\"\n [style.width.%]=\"readyPercent(m)\">\n </div>\n </div>\n <span class=\"c2g-mr__member-pct\" [class]=\"'c2g-mr__member-pct--' + readyColor(m)\">\n {{ readyPercent(m) }}%\n </span>\n </li>\n }\n </ul>\n }\n\n</div>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-mr{border-radius:var(--c2g-radius-xl, 20px);background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));overflow:hidden;transition:box-shadow .2s ease,transform .2s ease}.c2g-mr:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .12));transform:translateY(-2px)}.c2g-mr__header{display:flex;align-items:center;justify-content:space-between;padding:16px 18px 10px;gap:10px}.c2g-mr__title-col{display:flex;flex-direction:column;gap:2px;min-width:0}.c2g-mr__label{font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-mr__tour{font-size:.85rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c2g-mr__summary-pill{display:flex;align-items:baseline;gap:3px;background:var(--c2g-theme-surface-container, rgba(0, 0, 0, .04));border-radius:20px;padding:4px 12px;flex-shrink:0}.c2g-mr__summary-ready{font-size:1.1rem;font-weight:900;color:var(--c2g-theme-primary, #ff6b35);line-height:1}.c2g-mr__summary-sep{font-size:.8rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-mr__summary-total{font-size:.9rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}.c2g-mr__summary-unit{font-size:.7rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));margin-left:2px}.c2g-mr__overall-track{position:relative;margin:0 18px 12px;height:7px;border-radius:4px;background:var(--c2g-theme-outline-variant, rgba(0, 0, 0, .08));overflow:visible}.c2g-mr__overall-fill{height:100%;border-radius:4px;background:var(--c2g-theme-primary, #ff6b35);transition:width .7s cubic-bezier(.4,0,.2,1)}.c2g-mr__overall-fill--success{background:var(--c2g-theme-success, #22c55e)}.c2g-mr__overall-pct{position:absolute;right:0;top:-18px;font-size:.7rem;font-weight:700;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-mr__groups{display:flex;flex-direction:column;gap:2px;padding:0 10px 12px}.c2g-mr__group{border-radius:12px;overflow:hidden;transition:background .15s ease;background:var(--mr-group-bg)}.c2g-mr__group--success{--mr-accent: #22c55e;--mr-accent-bg: rgba(34, 197, 94, .12);--mr-group-bg: rgba(34, 197, 94, .08)}.c2g-mr__group--warning{--mr-accent: #f59e0b;--mr-accent-bg: rgba(245, 158, 11, .15);--mr-group-bg: rgba(245, 158, 11, .06)}.c2g-mr__group--danger{--mr-accent: #ef4444;--mr-accent-bg: rgba(239, 68, 68, .15);--mr-group-bg: rgba(239, 68, 68, .06)}.c2g-mr__group--neutral{--mr-accent: #9ca3af;--mr-accent-bg: rgba(156, 163, 175, .15);--mr-group-bg: rgba(156, 163, 175, .05)}.c2g-mr__group-header{width:100%;display:flex;align-items:center;gap:6px;padding:8px 10px;background:transparent;border:none;cursor:pointer;text-align:left;border-radius:12px;transition:background .15s ease}.c2g-mr__group-header:hover{background:var(--mr-accent-bg)}.c2g-mr__group-emoji{font-size:1rem;line-height:1;flex-shrink:0}.c2g-mr__group-name{font-size:.8rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));flex:1}.c2g-mr__group-count{font-size:.72rem;font-weight:800;min-width:18px;height:18px;border-radius:9px;background:var(--mr-accent-bg);color:var(--mr-accent);display:flex;align-items:center;justify-content:center;padding:0 5px}.c2g-mr__avatar-stack{display:flex;margin-left:4px}.c2g-mr__avatar-tiny{width:22px;height:22px;border-radius:50%;font-size:.55rem;font-weight:800;display:flex;align-items:center;justify-content:center;margin-left:-6px;border:2px solid var(--c2g-theme-surface, #fff);background:var(--mr-accent-bg);color:var(--mr-accent)}.c2g-mr__avatar-tiny:first-child{margin-left:0}.c2g-mr__avatar-tiny--more{background:var(--c2g-theme-outline-variant, rgba(0, 0, 0, .1));color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));font-size:.5rem}.c2g-mr__group-chevron{font-size:1.1rem;font-weight:700;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));transform:rotate(90deg);transition:transform .2s ease;line-height:1;margin-left:2px}.c2g-mr__group-chevron--open{transform:rotate(-90deg)}.c2g-mr__member-list,.c2g-mr__flat-list{list-style:none;margin:0;padding:0 10px 4px;display:flex;flex-direction:column;gap:2px}.c2g-mr__flat-list{padding:0 10px 14px}.c2g-mr__member{display:flex;align-items:center;gap:8px;padding:5px 6px;border-radius:8px;transition:background .15s ease}.c2g-mr__member:hover{background:var(--c2g-theme-surface-container, rgba(0, 0, 0, .03))}.c2g-mr__member--self{background:#ff6b350f}.c2g-mr__member--self:hover{background:#ff6b351a}.c2g-mr__avatar-sm{width:28px;height:28px;border-radius:50%;font-size:.65rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex-shrink:0}.c2g-mr__avatar-sm--success{background:#22c55e1f;color:#22c55e}.c2g-mr__avatar-sm--warning{background:#f59e0b26;color:#f59e0b}.c2g-mr__avatar-sm--danger{background:#ef444426;color:#ef4444}.c2g-mr__avatar-sm--neutral{background:#9ca3af26;color:#9ca3af}.c2g-mr__member-name{font-size:.8rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:flex;align-items:center;gap:4px}.c2g-mr__self-tag{font-size:.6rem;font-weight:800;text-transform:uppercase;padding:1px 5px;border-radius:4px;background:var(--c2g-theme-primary, #ff6b35);color:#fff;flex-shrink:0}.c2g-mr__mini-track{width:52px;height:4px;border-radius:2px;background:var(--c2g-theme-outline-variant, rgba(0, 0, 0, .08));overflow:hidden;flex-shrink:0}.c2g-mr__mini-fill{height:100%;border-radius:2px;transition:width .6s cubic-bezier(.4,0,.2,1)}.c2g-mr__mini-fill--success{background:#22c55e}.c2g-mr__mini-fill--warning{background:#f59e0b}.c2g-mr__mini-fill--danger{background:#ef4444}.c2g-mr__mini-fill--neutral{background:#9ca3af}.c2g-mr__member-pct{font-size:.72rem;font-weight:700;min-width:32px;text-align:right;flex-shrink:0}.c2g-mr__member-pct--success{color:#22c55e}.c2g-mr__member-pct--warning{color:#f59e0b}.c2g-mr__member-pct--danger{color:#ef4444}.c2g-mr__member-pct--neutral{color:#9ca3af}\n"] }]
|
|
1855
|
+
}], propDecorators: { members: [{ type: i0.Input, args: [{ isSignal: true, alias: "members", required: true }] }], tourName: [{ type: i0.Input, args: [{ isSignal: true, alias: "tourName", required: false }] }] } });
|
|
1856
|
+
|
|
1857
|
+
class StreakWidgetComponent {
|
|
1858
|
+
data = input.required(...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
1859
|
+
xpPercent = computed(() => {
|
|
1860
|
+
const d = this.data();
|
|
1861
|
+
if (d.nextLevelXp === 0)
|
|
1862
|
+
return 100;
|
|
1863
|
+
return Math.min(100, Math.round((d.experiencePoints / d.nextLevelXp) * 100));
|
|
1864
|
+
}, ...(ngDevMode ? [{ debugName: "xpPercent" }] : []));
|
|
1865
|
+
streakTier = computed(() => {
|
|
1866
|
+
const s = this.data().streakDays;
|
|
1867
|
+
if (s >= 365)
|
|
1868
|
+
return 'legendary';
|
|
1869
|
+
if (s >= 30)
|
|
1870
|
+
return 'hot';
|
|
1871
|
+
if (s >= 7)
|
|
1872
|
+
return 'warm';
|
|
1873
|
+
return 'cold';
|
|
1874
|
+
}, ...(ngDevMode ? [{ debugName: "streakTier" }] : []));
|
|
1875
|
+
streakEmoji = computed(() => {
|
|
1876
|
+
switch (this.streakTier()) {
|
|
1877
|
+
case 'legendary': return '🏆';
|
|
1878
|
+
case 'hot': return '🔥';
|
|
1879
|
+
case 'warm': return '⚡';
|
|
1880
|
+
default: return '🌱';
|
|
1881
|
+
}
|
|
1882
|
+
}, ...(ngDevMode ? [{ debugName: "streakEmoji" }] : []));
|
|
1883
|
+
levelLabel = computed(() => {
|
|
1884
|
+
const lvl = this.data().level;
|
|
1885
|
+
if (lvl >= 50)
|
|
1886
|
+
return 'Legende';
|
|
1887
|
+
if (lvl >= 30)
|
|
1888
|
+
return 'Experte';
|
|
1889
|
+
if (lvl >= 15)
|
|
1890
|
+
return 'Erfahren';
|
|
1891
|
+
if (lvl >= 5)
|
|
1892
|
+
return 'Fortgeschritten';
|
|
1893
|
+
return 'Einsteiger';
|
|
1894
|
+
}, ...(ngDevMode ? [{ debugName: "levelLabel" }] : []));
|
|
1895
|
+
streakLabel = computed(() => {
|
|
1896
|
+
const s = this.data().streakDays;
|
|
1897
|
+
if (s === 0)
|
|
1898
|
+
return 'Noch kein Streak';
|
|
1899
|
+
if (s === 1)
|
|
1900
|
+
return '1 Tag Streak';
|
|
1901
|
+
return `${s} Tage Streak`;
|
|
1902
|
+
}, ...(ngDevMode ? [{ debugName: "streakLabel" }] : []));
|
|
1903
|
+
xpToNext = computed(() => Math.max(0, this.data().nextLevelXp - this.data().experiencePoints), ...(ngDevMode ? [{ debugName: "xpToNext" }] : []));
|
|
1904
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: StreakWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1905
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: StreakWidgetComponent, isStandalone: true, selector: "c2g-streak-widget", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"c2g-streak\" [class]=\"'c2g-streak--' + streakTier()\">\n\n <div class=\"c2g-streak__header\">\n <span class=\"c2g-streak__title\">Abenteurer-Level</span>\n <span class=\"c2g-streak__level-badge\">Lvl {{ data().level }}</span>\n </div>\n\n <div class=\"c2g-streak__hero\">\n <span class=\"c2g-streak__emoji\">{{ streakEmoji() }}</span>\n <div class=\"c2g-streak__streak-info\">\n <span class=\"c2g-streak__streak-count\">{{ data().streakDays }}</span>\n <span class=\"c2g-streak__streak-unit\">{{ data().streakDays === 1 ? 'Tag' : 'Tage' }}</span>\n </div>\n </div>\n\n <div class=\"c2g-streak__level-row\">\n <span class=\"c2g-streak__level-label\">{{ levelLabel() }}</span>\n <span class=\"c2g-streak__xp-label\">{{ data().experiencePoints }} / {{ data().nextLevelXp }} XP</span>\n </div>\n\n <div class=\"c2g-streak__xp-track\">\n <div class=\"c2g-streak__xp-fill\" [style.width.%]=\"xpPercent()\"></div>\n </div>\n\n <div class=\"c2g-streak__stats\">\n @if (data().totalDistanceKm != null) {\n <div class=\"c2g-streak__stat\">\n <span class=\"c2g-streak__stat-value\">{{ data().totalDistanceKm }}</span>\n <span class=\"c2g-streak__stat-label\">km</span>\n </div>\n }\n @if (data().totalNightsCamped != null) {\n <div class=\"c2g-streak__stat\">\n <span class=\"c2g-streak__stat-value\">{{ data().totalNightsCamped }}</span>\n <span class=\"c2g-streak__stat-label\">N\u00E4chte</span>\n </div>\n }\n @if (data().favoriteSeason) {\n <div class=\"c2g-streak__stat\">\n <span class=\"c2g-streak__stat-value\">\n @switch (data().favoriteSeason) {\n @case ('spring') { \uD83C\uDF38 }\n @case ('summer') { \u2600\uFE0F }\n @case ('autumn') { \uD83C\uDF42 }\n @case ('winter') { \u2744\uFE0F }\n @default { \uD83C\uDFD5\uFE0F }\n }\n </span>\n <span class=\"c2g-streak__stat-label\">Lieblingszeit</span>\n </div>\n }\n </div>\n\n @if (xpToNext() > 0) {\n <div class=\"c2g-streak__next-level\">\n Noch {{ xpToNext() }} XP bis Level {{ data().level + 1 }}\n </div>\n } @else {\n <div class=\"c2g-streak__next-level c2g-streak__next-level--ready\">\n Level Up bereit! \uD83C\uDF89\n </div>\n }\n\n</div>\n", styles: [":host{display:block;transition:background-color var(--c2g-transition-medium, .25s ease),color var(--c2g-transition-medium, .25s ease)}.c2g-streak{border-radius:var(--c2g-radius-lg, 16px);padding:20px;background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif);transition:box-shadow .2s ease;position:relative;overflow:hidden}.c2g-streak:hover{box-shadow:var(--c2g-shadow-md, 0 4px 12px rgba(0, 0, 0, .1))}.c2g-streak:before{content:\"\";position:absolute;top:0;left:0;right:0;height:3px;background:var(--c2g-theme-outline-variant, var(--c2g-color-outline));transition:background .3s ease}.c2g-streak--warm:before{background:#f59e0b}.c2g-streak--hot:before{background:linear-gradient(90deg,#f97316,#ef4444)}.c2g-streak--legendary:before{background:linear-gradient(90deg,#a855f7,#ec4899,#f59e0b)}.c2g-streak__header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.c2g-streak__title{font-size:var(--c2g-font-size-sm, .875rem);font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}.c2g-streak__level-badge{font-size:.7rem;font-weight:800;padding:2px 8px;border-radius:20px;background:var(--c2g-theme-primary, var(--c2g-color-primary));color:#fff;letter-spacing:.02em}.c2g-streak--hot .c2g-streak__level-badge{background:#f97316}.c2g-streak--legendary .c2g-streak__level-badge{background:linear-gradient(135deg,#a855f7,#ec4899)}.c2g-streak__hero{display:flex;align-items:center;gap:12px;margin-bottom:12px}.c2g-streak__emoji{font-size:2.5rem;line-height:1;filter:drop-shadow(0 2px 6px rgba(0,0,0,.15))}.c2g-streak__streak-info{display:flex;align-items:baseline;gap:4px}.c2g-streak__streak-count{font-size:3rem;font-weight:900;line-height:1;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));transition:color .3s ease}.c2g-streak--warm .c2g-streak__streak-count{color:#f59e0b}.c2g-streak--hot .c2g-streak__streak-count{color:#f97316}.c2g-streak--legendary .c2g-streak__streak-count{color:#a855f7}.c2g-streak__streak-unit{font-size:1rem;font-weight:600;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));margin-bottom:4px}.c2g-streak__level-row{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.c2g-streak__level-label{font-size:.8rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}.c2g-streak__xp-label{font-size:.72rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-streak__xp-track{height:8px;border-radius:4px;background:var(--c2g-theme-outline-variant, var(--c2g-color-outline));overflow:hidden;margin-bottom:16px}.c2g-streak__xp-fill{height:100%;border-radius:4px;background:var(--c2g-theme-primary, var(--c2g-color-primary));transition:width .7s cubic-bezier(.4,0,.2,1)}.c2g-streak--warm .c2g-streak__xp-fill{background:#f59e0b}.c2g-streak--hot .c2g-streak__xp-fill{background:linear-gradient(90deg,#f97316,#ef4444)}.c2g-streak--legendary .c2g-streak__xp-fill{background:linear-gradient(90deg,#a855f7,#ec4899)}.c2g-streak__stats{display:flex;gap:0;border-radius:var(--c2g-radius-md, 10px);background:var(--c2g-theme-surface-container, rgba(0, 0, 0, .04));overflow:hidden;margin-bottom:12px}.c2g-streak__stat{flex:1;display:flex;flex-direction:column;align-items:center;gap:2px;padding:10px 8px;border-right:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline))}.c2g-streak__stat:last-child{border-right:none}.c2g-streak__stat-value{font-size:1.1rem;font-weight:800;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));line-height:1}.c2g-streak__stat-label{font-size:.65rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-streak__next-level{font-size:.75rem;font-weight:600;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));text-align:center}.c2g-streak__next-level--ready{color:var(--c2g-theme-success, var(--c2g-color-success));font-weight:700}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1906
|
+
}
|
|
1907
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: StreakWidgetComponent, decorators: [{
|
|
1908
|
+
type: Component,
|
|
1909
|
+
args: [{ selector: 'c2g-streak-widget', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"c2g-streak\" [class]=\"'c2g-streak--' + streakTier()\">\n\n <div class=\"c2g-streak__header\">\n <span class=\"c2g-streak__title\">Abenteurer-Level</span>\n <span class=\"c2g-streak__level-badge\">Lvl {{ data().level }}</span>\n </div>\n\n <div class=\"c2g-streak__hero\">\n <span class=\"c2g-streak__emoji\">{{ streakEmoji() }}</span>\n <div class=\"c2g-streak__streak-info\">\n <span class=\"c2g-streak__streak-count\">{{ data().streakDays }}</span>\n <span class=\"c2g-streak__streak-unit\">{{ data().streakDays === 1 ? 'Tag' : 'Tage' }}</span>\n </div>\n </div>\n\n <div class=\"c2g-streak__level-row\">\n <span class=\"c2g-streak__level-label\">{{ levelLabel() }}</span>\n <span class=\"c2g-streak__xp-label\">{{ data().experiencePoints }} / {{ data().nextLevelXp }} XP</span>\n </div>\n\n <div class=\"c2g-streak__xp-track\">\n <div class=\"c2g-streak__xp-fill\" [style.width.%]=\"xpPercent()\"></div>\n </div>\n\n <div class=\"c2g-streak__stats\">\n @if (data().totalDistanceKm != null) {\n <div class=\"c2g-streak__stat\">\n <span class=\"c2g-streak__stat-value\">{{ data().totalDistanceKm }}</span>\n <span class=\"c2g-streak__stat-label\">km</span>\n </div>\n }\n @if (data().totalNightsCamped != null) {\n <div class=\"c2g-streak__stat\">\n <span class=\"c2g-streak__stat-value\">{{ data().totalNightsCamped }}</span>\n <span class=\"c2g-streak__stat-label\">N\u00E4chte</span>\n </div>\n }\n @if (data().favoriteSeason) {\n <div class=\"c2g-streak__stat\">\n <span class=\"c2g-streak__stat-value\">\n @switch (data().favoriteSeason) {\n @case ('spring') { \uD83C\uDF38 }\n @case ('summer') { \u2600\uFE0F }\n @case ('autumn') { \uD83C\uDF42 }\n @case ('winter') { \u2744\uFE0F }\n @default { \uD83C\uDFD5\uFE0F }\n }\n </span>\n <span class=\"c2g-streak__stat-label\">Lieblingszeit</span>\n </div>\n }\n </div>\n\n @if (xpToNext() > 0) {\n <div class=\"c2g-streak__next-level\">\n Noch {{ xpToNext() }} XP bis Level {{ data().level + 1 }}\n </div>\n } @else {\n <div class=\"c2g-streak__next-level c2g-streak__next-level--ready\">\n Level Up bereit! \uD83C\uDF89\n </div>\n }\n\n</div>\n", styles: [":host{display:block;transition:background-color var(--c2g-transition-medium, .25s ease),color var(--c2g-transition-medium, .25s ease)}.c2g-streak{border-radius:var(--c2g-radius-lg, 16px);padding:20px;background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif);transition:box-shadow .2s ease;position:relative;overflow:hidden}.c2g-streak:hover{box-shadow:var(--c2g-shadow-md, 0 4px 12px rgba(0, 0, 0, .1))}.c2g-streak:before{content:\"\";position:absolute;top:0;left:0;right:0;height:3px;background:var(--c2g-theme-outline-variant, var(--c2g-color-outline));transition:background .3s ease}.c2g-streak--warm:before{background:#f59e0b}.c2g-streak--hot:before{background:linear-gradient(90deg,#f97316,#ef4444)}.c2g-streak--legendary:before{background:linear-gradient(90deg,#a855f7,#ec4899,#f59e0b)}.c2g-streak__header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.c2g-streak__title{font-size:var(--c2g-font-size-sm, .875rem);font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}.c2g-streak__level-badge{font-size:.7rem;font-weight:800;padding:2px 8px;border-radius:20px;background:var(--c2g-theme-primary, var(--c2g-color-primary));color:#fff;letter-spacing:.02em}.c2g-streak--hot .c2g-streak__level-badge{background:#f97316}.c2g-streak--legendary .c2g-streak__level-badge{background:linear-gradient(135deg,#a855f7,#ec4899)}.c2g-streak__hero{display:flex;align-items:center;gap:12px;margin-bottom:12px}.c2g-streak__emoji{font-size:2.5rem;line-height:1;filter:drop-shadow(0 2px 6px rgba(0,0,0,.15))}.c2g-streak__streak-info{display:flex;align-items:baseline;gap:4px}.c2g-streak__streak-count{font-size:3rem;font-weight:900;line-height:1;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));transition:color .3s ease}.c2g-streak--warm .c2g-streak__streak-count{color:#f59e0b}.c2g-streak--hot .c2g-streak__streak-count{color:#f97316}.c2g-streak--legendary .c2g-streak__streak-count{color:#a855f7}.c2g-streak__streak-unit{font-size:1rem;font-weight:600;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));margin-bottom:4px}.c2g-streak__level-row{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.c2g-streak__level-label{font-size:.8rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}.c2g-streak__xp-label{font-size:.72rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-streak__xp-track{height:8px;border-radius:4px;background:var(--c2g-theme-outline-variant, var(--c2g-color-outline));overflow:hidden;margin-bottom:16px}.c2g-streak__xp-fill{height:100%;border-radius:4px;background:var(--c2g-theme-primary, var(--c2g-color-primary));transition:width .7s cubic-bezier(.4,0,.2,1)}.c2g-streak--warm .c2g-streak__xp-fill{background:#f59e0b}.c2g-streak--hot .c2g-streak__xp-fill{background:linear-gradient(90deg,#f97316,#ef4444)}.c2g-streak--legendary .c2g-streak__xp-fill{background:linear-gradient(90deg,#a855f7,#ec4899)}.c2g-streak__stats{display:flex;gap:0;border-radius:var(--c2g-radius-md, 10px);background:var(--c2g-theme-surface-container, rgba(0, 0, 0, .04));overflow:hidden;margin-bottom:12px}.c2g-streak__stat{flex:1;display:flex;flex-direction:column;align-items:center;gap:2px;padding:10px 8px;border-right:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline))}.c2g-streak__stat:last-child{border-right:none}.c2g-streak__stat-value{font-size:1.1rem;font-weight:800;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));line-height:1}.c2g-streak__stat-label{font-size:.65rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-streak__next-level{font-size:.75rem;font-weight:600;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));text-align:center}.c2g-streak__next-level--ready{color:var(--c2g-theme-success, var(--c2g-color-success));font-weight:700}\n"] }]
|
|
1910
|
+
}], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }] } });
|
|
1911
|
+
|
|
1912
|
+
class CampingScoreWidgetComponent {
|
|
1913
|
+
data = input.required(...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
1914
|
+
tier = computed(() => {
|
|
1915
|
+
const s = this.data().score;
|
|
1916
|
+
if (s >= 8)
|
|
1917
|
+
return 'great';
|
|
1918
|
+
if (s >= 6)
|
|
1919
|
+
return 'good';
|
|
1920
|
+
if (s >= 4)
|
|
1921
|
+
return 'ok';
|
|
1922
|
+
return 'bad';
|
|
1923
|
+
}, ...(ngDevMode ? [{ debugName: "tier" }] : []));
|
|
1924
|
+
tierLabel = computed(() => {
|
|
1925
|
+
if (this.data().label)
|
|
1926
|
+
return this.data().label;
|
|
1927
|
+
switch (this.tier()) {
|
|
1928
|
+
case 'great': return 'Perfektes Camping-Wetter';
|
|
1929
|
+
case 'good': return 'Gute Bedingungen';
|
|
1930
|
+
case 'ok': return 'Akzeptables Wetter';
|
|
1931
|
+
default: return 'Schwierige Bedingungen';
|
|
1932
|
+
}
|
|
1933
|
+
}, ...(ngDevMode ? [{ debugName: "tierLabel" }] : []));
|
|
1934
|
+
tierEmoji = computed(() => {
|
|
1935
|
+
switch (this.tier()) {
|
|
1936
|
+
case 'great': return '☀️';
|
|
1937
|
+
case 'good': return '⛅';
|
|
1938
|
+
case 'ok': return '🌤';
|
|
1939
|
+
default: return '🌧️';
|
|
1940
|
+
}
|
|
1941
|
+
}, ...(ngDevMode ? [{ debugName: "tierEmoji" }] : []));
|
|
1942
|
+
factors = computed(() => this.data().factors ?? [], ...(ngDevMode ? [{ debugName: "factors" }] : []));
|
|
1943
|
+
factorBarPct(score) {
|
|
1944
|
+
return Math.min(score / 10, 1) * 100;
|
|
1945
|
+
}
|
|
1946
|
+
factorTier(score, inverted = false) {
|
|
1947
|
+
const s = inverted ? 10 - score : score;
|
|
1948
|
+
if (s >= 6.5)
|
|
1949
|
+
return 'good';
|
|
1950
|
+
if (s >= 3.5)
|
|
1951
|
+
return 'mid';
|
|
1952
|
+
return 'bad';
|
|
1953
|
+
}
|
|
1954
|
+
// Count-up animation
|
|
1955
|
+
displayScore = signal('0.0', ...(ngDevMode ? [{ debugName: "displayScore" }] : []));
|
|
1956
|
+
raf = 0;
|
|
1957
|
+
constructor() {
|
|
1958
|
+
effect(() => {
|
|
1959
|
+
const target = this.data().score;
|
|
1960
|
+
cancelAnimationFrame(this.raf);
|
|
1961
|
+
const start = performance.now();
|
|
1962
|
+
const tick = (now) => {
|
|
1963
|
+
const t = Math.min((now - start) / 900, 1);
|
|
1964
|
+
const e = 1 - Math.pow(1 - t, 3);
|
|
1965
|
+
this.displayScore.set((e * target).toFixed(1));
|
|
1966
|
+
if (t < 1)
|
|
1967
|
+
this.raf = requestAnimationFrame(tick);
|
|
1968
|
+
};
|
|
1969
|
+
this.raf = requestAnimationFrame(tick);
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1972
|
+
ngOnDestroy() { cancelAnimationFrame(this.raf); }
|
|
1973
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: CampingScoreWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1974
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: CampingScoreWidgetComponent, isStandalone: true, selector: "c2g-camping-score-widget", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<article class=\"c2g-cs\" [class]=\"'c2g-cs--' + tier()\">\n\n <!-- Ambient glow orb behind score -->\n <div class=\"c2g-cs__glow-orb\" aria-hidden=\"true\"></div>\n\n <div class=\"c2g-cs__header\">\n <div class=\"c2g-cs__title-col\">\n <span class=\"c2g-cs__label\">Camping-Score</span>\n @if (data().tourName) {\n <span class=\"c2g-cs__tour\">{{ data().tourName }}</span>\n }\n </div>\n <span class=\"c2g-cs__tier-badge\">\n {{ tierEmoji() }} {{ tierLabel() }}\n </span>\n </div>\n\n <!-- Score hero -->\n <div class=\"c2g-cs__score-hero\">\n <span class=\"c2g-cs__score-value\">{{ displayScore() }}</span>\n <div class=\"c2g-cs__score-meta\">\n <span class=\"c2g-cs__score-max\">/10</span>\n <!-- Progress bar -->\n <div class=\"c2g-cs__progress-track\">\n <div class=\"c2g-cs__progress-fill\"\n [style.width.%]=\"(data().score / 10) * 100\">\n </div>\n </div>\n </div>\n </div>\n\n <!-- Factor tiles (optional) -->\n @if (factors().length > 0) {\n <div class=\"c2g-cs__factors\">\n @for (f of factors(); track f.label) {\n <div class=\"c2g-cs__factor\"\n [class]=\"'c2g-cs__factor--' + factorTier(f.score, f.inverted)\"\n [class.c2g-cs__factor--inverted]=\"f.inverted\">\n <span class=\"c2g-cs__factor-direction\">{{ f.inverted ? '\u2193' : '\u2191' }}</span>\n <span class=\"c2g-cs__factor-icon\">{{ f.icon }}</span>\n <span class=\"c2g-cs__factor-label\">{{ f.label }}</span>\n <div class=\"c2g-cs__factor-bar-track\">\n <div class=\"c2g-cs__factor-bar-fill\"\n [style.width.%]=\"factorBarPct(f.score)\">\n </div>\n </div>\n <span class=\"c2g-cs__factor-score\">{{ f.score.toFixed(0) }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Highlights -->\n @if (data().highlights?.length) {\n <ul class=\"c2g-cs__highlights\">\n @for (h of data().highlights!.slice(0, 3); track h) {\n <li class=\"c2g-cs__highlight\">\n <span class=\"c2g-cs__highlight-dot\"></span>\n {{ h }}\n </li>\n }\n </ul>\n }\n\n <!-- Warnings -->\n @if (data().warnings?.length) {\n <div class=\"c2g-cs__warnings\">\n @for (w of data().warnings!.slice(0, 2); track w) {\n <div class=\"c2g-cs__warning\">\u26A0\uFE0F {{ w }}</div>\n }\n </div>\n }\n\n</article>\n", styles: [":host{display:block}.c2g-cs{background:color-mix(in srgb,var(--cs-color, #94a3b8) 6%,var(--c2g-theme-surface, var(--c2g-color-surface)));border:1.5px solid color-mix(in srgb,var(--cs-color, #94a3b8) 35%,transparent);border-radius:var(--c2g-radius-xl, 20px);padding:1.25rem 1.5rem 1.125rem;display:flex;flex-direction:column;gap:.875rem;position:relative;overflow:hidden;transition:box-shadow .2s ease,transform .2s ease}.c2g-cs:hover{transform:translateY(-2px);box-shadow:0 8px 32px var(--cs-glow, rgba(0, 0, 0, .12))}.c2g-cs--great{--cs-color: #22c55e;--cs-glow: rgba(34,197,94,.35);--cs-glow-soft: rgba(34,197,94,.1)}.c2g-cs--good{--cs-color: #84cc16;--cs-glow: rgba(132,204,22,.3);--cs-glow-soft: rgba(132,204,22,.08)}.c2g-cs--ok{--cs-color: #f59e0b;--cs-glow: rgba(245,158,11,.3);--cs-glow-soft: rgba(245,158,11,.08)}.c2g-cs--bad{--cs-color: #ef4444;--cs-glow: rgba(239,68,68,.3);--cs-glow-soft: rgba(239,68,68,.08)}.c2g-cs__glow-orb{position:absolute;top:-40px;right:-40px;width:160px;height:160px;border-radius:50%;background:radial-gradient(circle,var(--cs-glow-soft, transparent) 0%,transparent 70%);pointer-events:none}.c2g-cs__header{display:flex;align-items:flex-start;justify-content:space-between;gap:.75rem}.c2g-cs__title-col{display:flex;flex-direction:column;gap:2px}.c2g-cs__label{font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-secondary))}.c2g-cs__tour{font-size:.8125rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c2g-cs__tier-badge{display:inline-flex;align-items:center;gap:4px;font-size:.72rem;font-weight:700;color:var(--cs-color);background:color-mix(in srgb,var(--cs-color) 12%,transparent);border:1px solid color-mix(in srgb,var(--cs-color) 28%,transparent);border-radius:20px;padding:3px 10px;white-space:nowrap;flex-shrink:0}.c2g-cs__score-hero{display:flex;align-items:center;gap:1rem}.c2g-cs__score-value{font-size:4rem;font-weight:900;line-height:1;letter-spacing:-.04em;color:var(--cs-color);font-variant-numeric:tabular-nums;text-shadow:0 0 40px var(--cs-glow, transparent);flex-shrink:0}.c2g-cs__score-meta{flex:1;display:flex;flex-direction:column;gap:6px}.c2g-cs__score-max{font-size:1rem;font-weight:700;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));line-height:1}.c2g-cs__progress-track{height:8px;background:var(--c2g-theme-surface-container, rgba(0, 0, 0, .07));border-radius:4px;overflow:hidden}.c2g-cs__progress-fill{height:100%;background:var(--cs-color);border-radius:4px;transition:width .9s cubic-bezier(.4,0,.2,1);box-shadow:0 0 8px var(--cs-glow, transparent)}.c2g-cs__factors{display:grid;grid-template-columns:repeat(3,1fr);gap:6px}.c2g-cs__factor{display:flex;flex-direction:column;align-items:center;gap:4px;padding:8px 6px 7px;border-radius:12px;border:1px solid color-mix(in srgb,var(--ft-color) 30%,transparent);background:color-mix(in srgb,var(--ft-color) 8%,var(--c2g-theme-surface, var(--c2g-color-surface)));text-align:center;position:relative;--ft-color: #94a3b8}.c2g-cs__factor--good{--ft-color: #22c55e}.c2g-cs__factor--mid{--ft-color: #f59e0b}.c2g-cs__factor--bad{--ft-color: #ef4444}.c2g-cs__factor-direction{position:absolute;top:5px;right:6px;font-size:.6rem;font-weight:900;line-height:1;color:var(--ft-color);opacity:.75}.c2g-cs__factor-icon{font-size:1.2rem;line-height:1}.c2g-cs__factor-label{font-size:.64rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-cs__factor-bar-track{width:100%;height:4px;background:var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));border-radius:2px;overflow:hidden}.c2g-cs__factor-bar-fill{height:100%;background:var(--ft-color);border-radius:2px;transition:width .8s cubic-bezier(.4,0,.2,1)}.c2g-cs__factor-score{font-size:.8rem;font-weight:900;color:var(--ft-color);line-height:1}.c2g-cs__highlights{list-style:none;margin:0;padding:.625rem 0 0;border-top:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));display:flex;flex-direction:column;gap:.375rem}.c2g-cs__highlight{display:flex;align-items:center;gap:.5rem;font-size:.8rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}.c2g-cs__highlight-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0;background:var(--cs-color);box-shadow:0 0 6px var(--cs-glow, transparent)}.c2g-cs__warnings{display:flex;flex-direction:column;gap:.375rem}.c2g-cs__warning{font-size:.75rem;font-weight:600;color:#f59e0b;background:#f59e0b1a;border:1px solid rgba(245,158,11,.2);border-radius:8px;padding:.3rem .625rem}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1975
|
+
}
|
|
1976
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: CampingScoreWidgetComponent, decorators: [{
|
|
1977
|
+
type: Component,
|
|
1978
|
+
args: [{ selector: 'c2g-camping-score-widget', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<article class=\"c2g-cs\" [class]=\"'c2g-cs--' + tier()\">\n\n <!-- Ambient glow orb behind score -->\n <div class=\"c2g-cs__glow-orb\" aria-hidden=\"true\"></div>\n\n <div class=\"c2g-cs__header\">\n <div class=\"c2g-cs__title-col\">\n <span class=\"c2g-cs__label\">Camping-Score</span>\n @if (data().tourName) {\n <span class=\"c2g-cs__tour\">{{ data().tourName }}</span>\n }\n </div>\n <span class=\"c2g-cs__tier-badge\">\n {{ tierEmoji() }} {{ tierLabel() }}\n </span>\n </div>\n\n <!-- Score hero -->\n <div class=\"c2g-cs__score-hero\">\n <span class=\"c2g-cs__score-value\">{{ displayScore() }}</span>\n <div class=\"c2g-cs__score-meta\">\n <span class=\"c2g-cs__score-max\">/10</span>\n <!-- Progress bar -->\n <div class=\"c2g-cs__progress-track\">\n <div class=\"c2g-cs__progress-fill\"\n [style.width.%]=\"(data().score / 10) * 100\">\n </div>\n </div>\n </div>\n </div>\n\n <!-- Factor tiles (optional) -->\n @if (factors().length > 0) {\n <div class=\"c2g-cs__factors\">\n @for (f of factors(); track f.label) {\n <div class=\"c2g-cs__factor\"\n [class]=\"'c2g-cs__factor--' + factorTier(f.score, f.inverted)\"\n [class.c2g-cs__factor--inverted]=\"f.inverted\">\n <span class=\"c2g-cs__factor-direction\">{{ f.inverted ? '\u2193' : '\u2191' }}</span>\n <span class=\"c2g-cs__factor-icon\">{{ f.icon }}</span>\n <span class=\"c2g-cs__factor-label\">{{ f.label }}</span>\n <div class=\"c2g-cs__factor-bar-track\">\n <div class=\"c2g-cs__factor-bar-fill\"\n [style.width.%]=\"factorBarPct(f.score)\">\n </div>\n </div>\n <span class=\"c2g-cs__factor-score\">{{ f.score.toFixed(0) }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Highlights -->\n @if (data().highlights?.length) {\n <ul class=\"c2g-cs__highlights\">\n @for (h of data().highlights!.slice(0, 3); track h) {\n <li class=\"c2g-cs__highlight\">\n <span class=\"c2g-cs__highlight-dot\"></span>\n {{ h }}\n </li>\n }\n </ul>\n }\n\n <!-- Warnings -->\n @if (data().warnings?.length) {\n <div class=\"c2g-cs__warnings\">\n @for (w of data().warnings!.slice(0, 2); track w) {\n <div class=\"c2g-cs__warning\">\u26A0\uFE0F {{ w }}</div>\n }\n </div>\n }\n\n</article>\n", styles: [":host{display:block}.c2g-cs{background:color-mix(in srgb,var(--cs-color, #94a3b8) 6%,var(--c2g-theme-surface, var(--c2g-color-surface)));border:1.5px solid color-mix(in srgb,var(--cs-color, #94a3b8) 35%,transparent);border-radius:var(--c2g-radius-xl, 20px);padding:1.25rem 1.5rem 1.125rem;display:flex;flex-direction:column;gap:.875rem;position:relative;overflow:hidden;transition:box-shadow .2s ease,transform .2s ease}.c2g-cs:hover{transform:translateY(-2px);box-shadow:0 8px 32px var(--cs-glow, rgba(0, 0, 0, .12))}.c2g-cs--great{--cs-color: #22c55e;--cs-glow: rgba(34,197,94,.35);--cs-glow-soft: rgba(34,197,94,.1)}.c2g-cs--good{--cs-color: #84cc16;--cs-glow: rgba(132,204,22,.3);--cs-glow-soft: rgba(132,204,22,.08)}.c2g-cs--ok{--cs-color: #f59e0b;--cs-glow: rgba(245,158,11,.3);--cs-glow-soft: rgba(245,158,11,.08)}.c2g-cs--bad{--cs-color: #ef4444;--cs-glow: rgba(239,68,68,.3);--cs-glow-soft: rgba(239,68,68,.08)}.c2g-cs__glow-orb{position:absolute;top:-40px;right:-40px;width:160px;height:160px;border-radius:50%;background:radial-gradient(circle,var(--cs-glow-soft, transparent) 0%,transparent 70%);pointer-events:none}.c2g-cs__header{display:flex;align-items:flex-start;justify-content:space-between;gap:.75rem}.c2g-cs__title-col{display:flex;flex-direction:column;gap:2px}.c2g-cs__label{font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-secondary))}.c2g-cs__tour{font-size:.8125rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c2g-cs__tier-badge{display:inline-flex;align-items:center;gap:4px;font-size:.72rem;font-weight:700;color:var(--cs-color);background:color-mix(in srgb,var(--cs-color) 12%,transparent);border:1px solid color-mix(in srgb,var(--cs-color) 28%,transparent);border-radius:20px;padding:3px 10px;white-space:nowrap;flex-shrink:0}.c2g-cs__score-hero{display:flex;align-items:center;gap:1rem}.c2g-cs__score-value{font-size:4rem;font-weight:900;line-height:1;letter-spacing:-.04em;color:var(--cs-color);font-variant-numeric:tabular-nums;text-shadow:0 0 40px var(--cs-glow, transparent);flex-shrink:0}.c2g-cs__score-meta{flex:1;display:flex;flex-direction:column;gap:6px}.c2g-cs__score-max{font-size:1rem;font-weight:700;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));line-height:1}.c2g-cs__progress-track{height:8px;background:var(--c2g-theme-surface-container, rgba(0, 0, 0, .07));border-radius:4px;overflow:hidden}.c2g-cs__progress-fill{height:100%;background:var(--cs-color);border-radius:4px;transition:width .9s cubic-bezier(.4,0,.2,1);box-shadow:0 0 8px var(--cs-glow, transparent)}.c2g-cs__factors{display:grid;grid-template-columns:repeat(3,1fr);gap:6px}.c2g-cs__factor{display:flex;flex-direction:column;align-items:center;gap:4px;padding:8px 6px 7px;border-radius:12px;border:1px solid color-mix(in srgb,var(--ft-color) 30%,transparent);background:color-mix(in srgb,var(--ft-color) 8%,var(--c2g-theme-surface, var(--c2g-color-surface)));text-align:center;position:relative;--ft-color: #94a3b8}.c2g-cs__factor--good{--ft-color: #22c55e}.c2g-cs__factor--mid{--ft-color: #f59e0b}.c2g-cs__factor--bad{--ft-color: #ef4444}.c2g-cs__factor-direction{position:absolute;top:5px;right:6px;font-size:.6rem;font-weight:900;line-height:1;color:var(--ft-color);opacity:.75}.c2g-cs__factor-icon{font-size:1.2rem;line-height:1}.c2g-cs__factor-label{font-size:.64rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-cs__factor-bar-track{width:100%;height:4px;background:var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));border-radius:2px;overflow:hidden}.c2g-cs__factor-bar-fill{height:100%;background:var(--ft-color);border-radius:2px;transition:width .8s cubic-bezier(.4,0,.2,1)}.c2g-cs__factor-score{font-size:.8rem;font-weight:900;color:var(--ft-color);line-height:1}.c2g-cs__highlights{list-style:none;margin:0;padding:.625rem 0 0;border-top:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));display:flex;flex-direction:column;gap:.375rem}.c2g-cs__highlight{display:flex;align-items:center;gap:.5rem;font-size:.8rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}.c2g-cs__highlight-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0;background:var(--cs-color);box-shadow:0 0 6px var(--cs-glow, transparent)}.c2g-cs__warnings{display:flex;flex-direction:column;gap:.375rem}.c2g-cs__warning{font-size:.75rem;font-weight:600;color:#f59e0b;background:#f59e0b1a;border:1px solid rgba(245,158,11,.2);border-radius:8px;padding:.3rem .625rem}\n"] }]
|
|
1979
|
+
}], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }] } });
|
|
1980
|
+
|
|
1981
|
+
class RainVisualizationWidgetComponent {
|
|
1982
|
+
data = input.required(...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
1983
|
+
enriched = computed(() => {
|
|
1984
|
+
const raw = this.data().days.slice(0, 7);
|
|
1985
|
+
const maxPrecip = Math.max(...raw.map(d => d.precipitation), 5);
|
|
1986
|
+
return raw.map(d => {
|
|
1987
|
+
const prob = d.precipitationProbability;
|
|
1988
|
+
const level = prob < 0.2 ? 'none' :
|
|
1989
|
+
prob < 0.5 ? 'light' :
|
|
1990
|
+
prob < 0.75 ? 'moderate' : 'heavy';
|
|
1991
|
+
return {
|
|
1992
|
+
...d,
|
|
1993
|
+
dayLabel: new Date(d.date).toLocaleDateString('de-DE', { weekday: 'short', day: 'numeric' }),
|
|
1994
|
+
probPct: Math.round(prob * 100),
|
|
1995
|
+
barPct: Math.round((d.precipitation / maxPrecip) * 100),
|
|
1996
|
+
level,
|
|
1997
|
+
icon: d.icon ?? (level === 'none' ? '☀️' : level === 'light' ? '🌦' : level === 'moderate' ? '🌧' : '⛈'),
|
|
1998
|
+
};
|
|
1999
|
+
});
|
|
2000
|
+
}, ...(ngDevMode ? [{ debugName: "enriched" }] : []));
|
|
2001
|
+
overallRisk = computed(() => {
|
|
2002
|
+
const days = this.enriched();
|
|
2003
|
+
const avg = days.reduce((s, d) => s + d.precipitationProbability, 0) / (days.length || 1);
|
|
2004
|
+
return avg >= 0.6 ? 'high' : avg >= 0.3 ? 'medium' : 'low';
|
|
2005
|
+
}, ...(ngDevMode ? [{ debugName: "overallRisk" }] : []));
|
|
2006
|
+
riskLabel = computed(() => {
|
|
2007
|
+
const days = this.enriched();
|
|
2008
|
+
const rainyDays = days.filter(d => d.precipitationProbability >= 0.4).length;
|
|
2009
|
+
const totalMm = days.reduce((s, d) => s + d.precipitation, 0);
|
|
2010
|
+
if (this.overallRisk() === 'high')
|
|
2011
|
+
return `${rainyDays} Regentage · ${totalMm.toFixed(0)} mm gesamt`;
|
|
2012
|
+
if (this.overallRisk() === 'medium')
|
|
2013
|
+
return `${rainyDays} Schauertag${rainyDays !== 1 ? 'e' : ''} möglich`;
|
|
2014
|
+
return 'Kaum Niederschlag erwartet';
|
|
2015
|
+
}, ...(ngDevMode ? [{ debugName: "riskLabel" }] : []));
|
|
2016
|
+
summaryIcon = computed(() => {
|
|
2017
|
+
switch (this.overallRisk()) {
|
|
2018
|
+
case 'high': return '⛈';
|
|
2019
|
+
case 'medium': return '🌦';
|
|
2020
|
+
default: return '☀️';
|
|
2021
|
+
}
|
|
2022
|
+
}, ...(ngDevMode ? [{ debugName: "summaryIcon" }] : []));
|
|
2023
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: RainVisualizationWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2024
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: RainVisualizationWidgetComponent, isStandalone: true, selector: "c2g-rain-visualization-widget", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<article class=\"c2g-rain\" [class]=\"'c2g-rain--' + overallRisk()\">\n\n <header class=\"c2g-rain__header\">\n <div class=\"c2g-rain__title-col\">\n <span class=\"c2g-rain__label\">\uD83C\uDF27 Niederschlag</span>\n @if (data().tourName) {\n <span class=\"c2g-rain__tour\">{{ data().tourName }}</span>\n }\n </div>\n <div class=\"c2g-rain__summary\">\n <span class=\"c2g-rain__summary-icon\">{{ summaryIcon() }}</span>\n <span class=\"c2g-rain__summary-text\">{{ riskLabel() }}</span>\n </div>\n </header>\n\n <!-- Bar chart grid -->\n <div class=\"c2g-rain__grid\" aria-label=\"Niederschlagsvorhersage\" role=\"list\">\n @for (day of enriched(); track day.date) {\n <div class=\"c2g-rain__day c2g-rain__day--{{ day.level }}\" role=\"listitem\">\n\n <!-- Weather icon -->\n <span class=\"c2g-rain__day-icon\">{{ day.icon }}</span>\n\n <!-- Probability badge -->\n <span class=\"c2g-rain__prob c2g-rain__prob--{{ day.level }}\">\n {{ day.probPct }}%\n </span>\n\n <!-- Bar (precipitation mm) -->\n <div class=\"c2g-rain__bar-wrap\" [title]=\"day.precipitation + ' mm'\">\n <div\n class=\"c2g-rain__bar c2g-rain__bar--{{ day.level }}\"\n [style.height.%]=\"day.barPct\"\n ></div>\n </div>\n\n @if (day.precipitation > 0) {\n <span class=\"c2g-rain__mm\">{{ day.precipitation.toFixed(1) }}<small>mm</small></span>\n } @else {\n <span class=\"c2g-rain__mm c2g-rain__mm--dry\">\u2013</span>\n }\n\n <!-- Day label -->\n <span class=\"c2g-rain__date\">{{ day.dayLabel }}</span>\n </div>\n }\n </div>\n\n</article>\n", styles: [":host{display:block}.c2g-rain{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));border-radius:var(--c2g-radius-xl, 20px);padding:1.25rem 1.5rem 1rem;display:flex;flex-direction:column;gap:1rem;position:relative;overflow:hidden;transition:box-shadow .2s ease,transform .2s ease}.c2g-rain:hover{transform:translateY(-2px);box-shadow:0 8px 32px #0000003d}.c2g-rain--low{--rain-color: #60a5fa;--rain-glow: rgba(96,165,250,.25)}.c2g-rain--medium{--rain-color: #3b82f6;--rain-glow: rgba(59,130,246,.3)}.c2g-rain--high{--rain-color: #1d4ed8;--rain-glow: rgba(29,78,216,.35)}.c2g-rain:before{content:\"\";position:absolute;inset:0;background:radial-gradient(ellipse 80% 50% at 50% 0%,rgba(59,130,246,.06) 0%,transparent 70%);pointer-events:none}.c2g-rain__header{display:flex;align-items:flex-start;justify-content:space-between;gap:.75rem}.c2g-rain__title-col{display:flex;flex-direction:column;gap:.125rem}.c2g-rain__label{font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-secondary))}.c2g-rain__tour{font-size:.8125rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c2g-rain__summary{display:flex;flex-direction:column;align-items:flex-end;gap:.125rem}.c2g-rain__summary-icon{font-size:1.5rem;line-height:1}.c2g-rain__summary-text{font-size:.6875rem;font-weight:600;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));text-align:right;max-width:140px}.c2g-rain__grid{display:flex;gap:.375rem;align-items:flex-end}.c2g-rain__day{flex:1;display:flex;flex-direction:column;align-items:center;gap:.25rem}.c2g-rain__day-icon{font-size:1.125rem;line-height:1}.c2g-rain__prob{font-size:.6875rem;font-weight:800;font-variant-numeric:tabular-nums;border-radius:6px;padding:.1rem .3rem}.c2g-rain__prob--none{color:#ffffff4d;background:#ffffff0a}.c2g-rain__prob--light{color:#93c5fd;background:#93c5fd1f}.c2g-rain__prob--moderate{color:#60a5fa;background:#60a5fa26}.c2g-rain__prob--heavy{color:#3b82f6;background:#3b82f633;box-shadow:0 0 8px #3b82f64d}.c2g-rain__bar-wrap{width:100%;height:72px;display:flex;align-items:flex-end;background:var(--c2g-theme-surface-container-low, var(--c2g-color-bg-base));border-radius:6px 6px 4px 4px;overflow:hidden}.c2g-rain__bar{width:100%;min-height:3px;border-radius:4px 4px 0 0;transition:height .7s cubic-bezier(.4,0,.2,1)}.c2g-rain__bar--none{background:#ffffff14}.c2g-rain__bar--light{background:linear-gradient(180deg,#93c5fd,#bfdbfe)}.c2g-rain__bar--moderate{background:linear-gradient(180deg,#3b82f6,#60a5fa)}.c2g-rain__bar--heavy{background:linear-gradient(180deg,#1d4ed8,#3b82f6);box-shadow:0 0 12px #3b82f666}.c2g-rain__mm{font-size:.6875rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));font-variant-numeric:tabular-nums;text-align:center}.c2g-rain__mm small{font-size:.5625rem;font-weight:600;opacity:.6;margin-left:1px}.c2g-rain__mm--dry{color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-rain__date{font-size:.625rem;font-weight:700;text-transform:uppercase;letter-spacing:.03em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));white-space:nowrap;text-align:center}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2025
|
+
}
|
|
2026
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: RainVisualizationWidgetComponent, decorators: [{
|
|
2027
|
+
type: Component,
|
|
2028
|
+
args: [{ selector: 'c2g-rain-visualization-widget', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<article class=\"c2g-rain\" [class]=\"'c2g-rain--' + overallRisk()\">\n\n <header class=\"c2g-rain__header\">\n <div class=\"c2g-rain__title-col\">\n <span class=\"c2g-rain__label\">\uD83C\uDF27 Niederschlag</span>\n @if (data().tourName) {\n <span class=\"c2g-rain__tour\">{{ data().tourName }}</span>\n }\n </div>\n <div class=\"c2g-rain__summary\">\n <span class=\"c2g-rain__summary-icon\">{{ summaryIcon() }}</span>\n <span class=\"c2g-rain__summary-text\">{{ riskLabel() }}</span>\n </div>\n </header>\n\n <!-- Bar chart grid -->\n <div class=\"c2g-rain__grid\" aria-label=\"Niederschlagsvorhersage\" role=\"list\">\n @for (day of enriched(); track day.date) {\n <div class=\"c2g-rain__day c2g-rain__day--{{ day.level }}\" role=\"listitem\">\n\n <!-- Weather icon -->\n <span class=\"c2g-rain__day-icon\">{{ day.icon }}</span>\n\n <!-- Probability badge -->\n <span class=\"c2g-rain__prob c2g-rain__prob--{{ day.level }}\">\n {{ day.probPct }}%\n </span>\n\n <!-- Bar (precipitation mm) -->\n <div class=\"c2g-rain__bar-wrap\" [title]=\"day.precipitation + ' mm'\">\n <div\n class=\"c2g-rain__bar c2g-rain__bar--{{ day.level }}\"\n [style.height.%]=\"day.barPct\"\n ></div>\n </div>\n\n @if (day.precipitation > 0) {\n <span class=\"c2g-rain__mm\">{{ day.precipitation.toFixed(1) }}<small>mm</small></span>\n } @else {\n <span class=\"c2g-rain__mm c2g-rain__mm--dry\">\u2013</span>\n }\n\n <!-- Day label -->\n <span class=\"c2g-rain__date\">{{ day.dayLabel }}</span>\n </div>\n }\n </div>\n\n</article>\n", styles: [":host{display:block}.c2g-rain{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));border-radius:var(--c2g-radius-xl, 20px);padding:1.25rem 1.5rem 1rem;display:flex;flex-direction:column;gap:1rem;position:relative;overflow:hidden;transition:box-shadow .2s ease,transform .2s ease}.c2g-rain:hover{transform:translateY(-2px);box-shadow:0 8px 32px #0000003d}.c2g-rain--low{--rain-color: #60a5fa;--rain-glow: rgba(96,165,250,.25)}.c2g-rain--medium{--rain-color: #3b82f6;--rain-glow: rgba(59,130,246,.3)}.c2g-rain--high{--rain-color: #1d4ed8;--rain-glow: rgba(29,78,216,.35)}.c2g-rain:before{content:\"\";position:absolute;inset:0;background:radial-gradient(ellipse 80% 50% at 50% 0%,rgba(59,130,246,.06) 0%,transparent 70%);pointer-events:none}.c2g-rain__header{display:flex;align-items:flex-start;justify-content:space-between;gap:.75rem}.c2g-rain__title-col{display:flex;flex-direction:column;gap:.125rem}.c2g-rain__label{font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-secondary))}.c2g-rain__tour{font-size:.8125rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c2g-rain__summary{display:flex;flex-direction:column;align-items:flex-end;gap:.125rem}.c2g-rain__summary-icon{font-size:1.5rem;line-height:1}.c2g-rain__summary-text{font-size:.6875rem;font-weight:600;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));text-align:right;max-width:140px}.c2g-rain__grid{display:flex;gap:.375rem;align-items:flex-end}.c2g-rain__day{flex:1;display:flex;flex-direction:column;align-items:center;gap:.25rem}.c2g-rain__day-icon{font-size:1.125rem;line-height:1}.c2g-rain__prob{font-size:.6875rem;font-weight:800;font-variant-numeric:tabular-nums;border-radius:6px;padding:.1rem .3rem}.c2g-rain__prob--none{color:#ffffff4d;background:#ffffff0a}.c2g-rain__prob--light{color:#93c5fd;background:#93c5fd1f}.c2g-rain__prob--moderate{color:#60a5fa;background:#60a5fa26}.c2g-rain__prob--heavy{color:#3b82f6;background:#3b82f633;box-shadow:0 0 8px #3b82f64d}.c2g-rain__bar-wrap{width:100%;height:72px;display:flex;align-items:flex-end;background:var(--c2g-theme-surface-container-low, var(--c2g-color-bg-base));border-radius:6px 6px 4px 4px;overflow:hidden}.c2g-rain__bar{width:100%;min-height:3px;border-radius:4px 4px 0 0;transition:height .7s cubic-bezier(.4,0,.2,1)}.c2g-rain__bar--none{background:#ffffff14}.c2g-rain__bar--light{background:linear-gradient(180deg,#93c5fd,#bfdbfe)}.c2g-rain__bar--moderate{background:linear-gradient(180deg,#3b82f6,#60a5fa)}.c2g-rain__bar--heavy{background:linear-gradient(180deg,#1d4ed8,#3b82f6);box-shadow:0 0 12px #3b82f666}.c2g-rain__mm{font-size:.6875rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));font-variant-numeric:tabular-nums;text-align:center}.c2g-rain__mm small{font-size:.5625rem;font-weight:600;opacity:.6;margin-left:1px}.c2g-rain__mm--dry{color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-rain__date{font-size:.625rem;font-weight:700;text-transform:uppercase;letter-spacing:.03em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));white-space:nowrap;text-align:center}\n"] }]
|
|
2029
|
+
}], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }] } });
|
|
2030
|
+
|
|
2031
|
+
const TYPE_CONFIG$1 = {
|
|
2032
|
+
adult: { emoji: '🧑', label: 'Erwachsene', color: '#ff6b35' },
|
|
2033
|
+
senior: { emoji: '👴', label: 'Senioren', color: '#a855f7' },
|
|
2034
|
+
teen: { emoji: '🧒', label: 'Jugendliche', color: '#f59e0b' },
|
|
2035
|
+
child: { emoji: '👧', label: 'Kinder', color: '#84cc16' },
|
|
2036
|
+
toddler: { emoji: '👶', label: 'Kleinkinder', color: '#06b6d4' },
|
|
2037
|
+
dog: { emoji: '🐕', label: 'Hunde', color: '#f97316' },
|
|
2038
|
+
cat: { emoji: '🐈', label: 'Katzen', color: '#ec4899' },
|
|
2039
|
+
pet: { emoji: '🐾', label: 'Haustiere', color: '#6b7280' },
|
|
2040
|
+
};
|
|
2041
|
+
const TYPE_ORDER = ['adult', 'senior', 'teen', 'child', 'toddler', 'dog', 'cat', 'pet'];
|
|
2042
|
+
class GroupCompositionWidgetComponent {
|
|
2043
|
+
data = input.required(...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
2044
|
+
sorted = computed(() => [...this.data().members]
|
|
2045
|
+
.filter(m => m.count > 0)
|
|
2046
|
+
.sort((a, b) => TYPE_ORDER.indexOf(a.type) - TYPE_ORDER.indexOf(b.type)), ...(ngDevMode ? [{ debugName: "sorted" }] : []));
|
|
2047
|
+
totalPeople = computed(() => this.data().members
|
|
2048
|
+
.filter(m => ['adult', 'teen', 'child', 'toddler', 'senior'].includes(m.type))
|
|
2049
|
+
.reduce((s, m) => s + m.count, 0), ...(ngDevMode ? [{ debugName: "totalPeople" }] : []));
|
|
2050
|
+
totalPets = computed(() => this.data().members
|
|
2051
|
+
.filter(m => ['dog', 'cat', 'pet'].includes(m.type))
|
|
2052
|
+
.reduce((s, m) => s + m.count, 0), ...(ngDevMode ? [{ debugName: "totalPets" }] : []));
|
|
2053
|
+
maxCount = computed(() => Math.max(...this.data().members.map(m => m.count), 1), ...(ngDevMode ? [{ debugName: "maxCount" }] : []));
|
|
2054
|
+
// Avatar cluster: up to 12 individual icons for the visual summary strip
|
|
2055
|
+
avatars = computed(() => {
|
|
2056
|
+
const result = [];
|
|
2057
|
+
for (const entry of this.sorted()) {
|
|
2058
|
+
const cfg = TYPE_CONFIG$1[entry.type];
|
|
2059
|
+
const n = Math.min(entry.count, 6);
|
|
2060
|
+
for (let i = 0; i < n; i++) {
|
|
2061
|
+
result.push({ emoji: cfg.emoji, color: cfg.color, key: `${entry.type}-${i}` });
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
return result.slice(0, 14);
|
|
2065
|
+
}, ...(ngDevMode ? [{ debugName: "avatars" }] : []));
|
|
2066
|
+
configFor(type) {
|
|
2067
|
+
return TYPE_CONFIG$1[type];
|
|
2068
|
+
}
|
|
2069
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GroupCompositionWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2070
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: GroupCompositionWidgetComponent, isStandalone: true, selector: "c2g-group-composition-widget", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"c2g-gc\">\n\n <div class=\"c2g-gc__header\">\n <div class=\"c2g-gc__title-col\">\n <span class=\"c2g-gc__label\">Gruppenkomposition</span>\n @if (data().groupName) {\n <span class=\"c2g-gc__name\">{{ data().groupName }}</span>\n }\n </div>\n <div class=\"c2g-gc__summary-pills\">\n <span class=\"c2g-gc__pill\">\n <span class=\"c2g-gc__pill-num\">{{ totalPeople() }}</span>\n <span class=\"c2g-gc__pill-sub\">Personen</span>\n </span>\n @if (totalPets() > 0) {\n <span class=\"c2g-gc__pill c2g-gc__pill--pets\">\n <span class=\"c2g-gc__pill-num\">{{ totalPets() }}</span>\n <span class=\"c2g-gc__pill-sub\">Tiere</span>\n </span>\n }\n </div>\n </div>\n\n <!-- Avatar cluster strip -->\n <div class=\"c2g-gc__avatars\">\n @for (a of avatars(); track a.key) {\n <span class=\"c2g-gc__avatar\" [style.--av-color]=\"a.color\">{{ a.emoji }}</span>\n }\n @if (totalPeople() + totalPets() > avatars().length) {\n <span class=\"c2g-gc__avatar-more\">+{{ totalPeople() + totalPets() - avatars().length }}</span>\n }\n </div>\n\n <!-- Bar chart breakdown -->\n <div class=\"c2g-gc__bars\">\n @for (entry of sorted(); track entry.type) {\n <div class=\"c2g-gc__bar-row\">\n <span class=\"c2g-gc__bar-emoji\">{{ configFor(entry.type).emoji }}</span>\n <span class=\"c2g-gc__bar-label\">{{ configFor(entry.type).label }}</span>\n <div class=\"c2g-gc__bar-track\">\n <div class=\"c2g-gc__bar-fill\"\n [style.width.%]=\"(entry.count / maxCount()) * 100\"\n [style.background]=\"configFor(entry.type).color\">\n </div>\n </div>\n <span class=\"c2g-gc__bar-count\" [style.color]=\"configFor(entry.type).color\">\n {{ entry.count }}\n </span>\n @if (entry.names?.length) {\n <span class=\"c2g-gc__bar-names\">{{ entry.names!.join(', ') }}</span>\n }\n </div>\n }\n </div>\n\n</div>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-gc{border-radius:var(--c2g-radius-xl, 20px);background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));padding:18px 20px 16px;display:flex;flex-direction:column;gap:14px;transition:box-shadow .2s ease,transform .2s ease}.c2g-gc:hover{box-shadow:0 8px 28px #0000001c;transform:translateY(-2px)}.c2g-gc__header{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}.c2g-gc__title-col{display:flex;flex-direction:column;gap:2px}.c2g-gc__label{font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-gc__name{font-size:.9rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}.c2g-gc__summary-pills{display:flex;gap:6px;align-items:center}.c2g-gc__pill{display:flex;flex-direction:column;align-items:center;background:var(--c2g-theme-surface-container, rgba(0, 0, 0, .04));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));border-radius:10px;padding:4px 10px;min-width:44px}.c2g-gc__pill--pets{background:#f9731614;border-color:#f9731633}.c2g-gc__pill-num{font-size:1.1rem;font-weight:900;line-height:1;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}.c2g-gc__pill-sub{font-size:.6rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-gc__avatars{display:flex;flex-wrap:wrap;gap:0;padding:10px 12px;background:var(--c2g-theme-surface-container, rgba(0, 0, 0, .025));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));border-radius:var(--c2g-radius-lg, 14px);min-height:48px;align-items:center}.c2g-gc__avatar{font-size:1.5rem;line-height:1;display:inline-block;margin-right:-4px;filter:drop-shadow(0 1px 3px color-mix(in srgb,var(--av-color, #000) 40%,transparent));transition:transform .15s ease,z-index 0ms;cursor:default;position:relative;z-index:1}.c2g-gc__avatar:hover{transform:translateY(-4px) scale(1.2);z-index:10}.c2g-gc__avatar-more{font-size:.75rem;font-weight:800;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));background:var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));border-radius:20px;padding:2px 7px;margin-left:6px}.c2g-gc__bars{display:flex;flex-direction:column;gap:6px}.c2g-gc__bar-row{display:grid;grid-template-columns:20px 90px 1fr 24px;grid-template-rows:auto auto;column-gap:8px;align-items:center}.c2g-gc__bar-emoji{font-size:1rem;line-height:1;grid-column:1;grid-row:1;text-align:center}.c2g-gc__bar-label{font-size:.78rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));grid-column:2;grid-row:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.c2g-gc__bar-track{grid-column:3;grid-row:1;height:8px;background:var(--c2g-theme-surface-container, rgba(0, 0, 0, .06));border-radius:4px;overflow:hidden}.c2g-gc__bar-fill{height:100%;border-radius:4px;transition:width .6s cubic-bezier(.4,0,.2,1);opacity:.85}.c2g-gc__bar-count{grid-column:4;grid-row:1;font-size:.85rem;font-weight:900;text-align:right;line-height:1}.c2g-gc__bar-names{grid-column:2/5;grid-row:2;font-size:.68rem;font-weight:500;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));padding-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2071
|
+
}
|
|
2072
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GroupCompositionWidgetComponent, decorators: [{
|
|
2073
|
+
type: Component,
|
|
2074
|
+
args: [{ selector: 'c2g-group-composition-widget', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"c2g-gc\">\n\n <div class=\"c2g-gc__header\">\n <div class=\"c2g-gc__title-col\">\n <span class=\"c2g-gc__label\">Gruppenkomposition</span>\n @if (data().groupName) {\n <span class=\"c2g-gc__name\">{{ data().groupName }}</span>\n }\n </div>\n <div class=\"c2g-gc__summary-pills\">\n <span class=\"c2g-gc__pill\">\n <span class=\"c2g-gc__pill-num\">{{ totalPeople() }}</span>\n <span class=\"c2g-gc__pill-sub\">Personen</span>\n </span>\n @if (totalPets() > 0) {\n <span class=\"c2g-gc__pill c2g-gc__pill--pets\">\n <span class=\"c2g-gc__pill-num\">{{ totalPets() }}</span>\n <span class=\"c2g-gc__pill-sub\">Tiere</span>\n </span>\n }\n </div>\n </div>\n\n <!-- Avatar cluster strip -->\n <div class=\"c2g-gc__avatars\">\n @for (a of avatars(); track a.key) {\n <span class=\"c2g-gc__avatar\" [style.--av-color]=\"a.color\">{{ a.emoji }}</span>\n }\n @if (totalPeople() + totalPets() > avatars().length) {\n <span class=\"c2g-gc__avatar-more\">+{{ totalPeople() + totalPets() - avatars().length }}</span>\n }\n </div>\n\n <!-- Bar chart breakdown -->\n <div class=\"c2g-gc__bars\">\n @for (entry of sorted(); track entry.type) {\n <div class=\"c2g-gc__bar-row\">\n <span class=\"c2g-gc__bar-emoji\">{{ configFor(entry.type).emoji }}</span>\n <span class=\"c2g-gc__bar-label\">{{ configFor(entry.type).label }}</span>\n <div class=\"c2g-gc__bar-track\">\n <div class=\"c2g-gc__bar-fill\"\n [style.width.%]=\"(entry.count / maxCount()) * 100\"\n [style.background]=\"configFor(entry.type).color\">\n </div>\n </div>\n <span class=\"c2g-gc__bar-count\" [style.color]=\"configFor(entry.type).color\">\n {{ entry.count }}\n </span>\n @if (entry.names?.length) {\n <span class=\"c2g-gc__bar-names\">{{ entry.names!.join(', ') }}</span>\n }\n </div>\n }\n </div>\n\n</div>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-gc{border-radius:var(--c2g-radius-xl, 20px);background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));padding:18px 20px 16px;display:flex;flex-direction:column;gap:14px;transition:box-shadow .2s ease,transform .2s ease}.c2g-gc:hover{box-shadow:0 8px 28px #0000001c;transform:translateY(-2px)}.c2g-gc__header{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}.c2g-gc__title-col{display:flex;flex-direction:column;gap:2px}.c2g-gc__label{font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-gc__name{font-size:.9rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}.c2g-gc__summary-pills{display:flex;gap:6px;align-items:center}.c2g-gc__pill{display:flex;flex-direction:column;align-items:center;background:var(--c2g-theme-surface-container, rgba(0, 0, 0, .04));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));border-radius:10px;padding:4px 10px;min-width:44px}.c2g-gc__pill--pets{background:#f9731614;border-color:#f9731633}.c2g-gc__pill-num{font-size:1.1rem;font-weight:900;line-height:1;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}.c2g-gc__pill-sub{font-size:.6rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-gc__avatars{display:flex;flex-wrap:wrap;gap:0;padding:10px 12px;background:var(--c2g-theme-surface-container, rgba(0, 0, 0, .025));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));border-radius:var(--c2g-radius-lg, 14px);min-height:48px;align-items:center}.c2g-gc__avatar{font-size:1.5rem;line-height:1;display:inline-block;margin-right:-4px;filter:drop-shadow(0 1px 3px color-mix(in srgb,var(--av-color, #000) 40%,transparent));transition:transform .15s ease,z-index 0ms;cursor:default;position:relative;z-index:1}.c2g-gc__avatar:hover{transform:translateY(-4px) scale(1.2);z-index:10}.c2g-gc__avatar-more{font-size:.75rem;font-weight:800;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));background:var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));border-radius:20px;padding:2px 7px;margin-left:6px}.c2g-gc__bars{display:flex;flex-direction:column;gap:6px}.c2g-gc__bar-row{display:grid;grid-template-columns:20px 90px 1fr 24px;grid-template-rows:auto auto;column-gap:8px;align-items:center}.c2g-gc__bar-emoji{font-size:1rem;line-height:1;grid-column:1;grid-row:1;text-align:center}.c2g-gc__bar-label{font-size:.78rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));grid-column:2;grid-row:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.c2g-gc__bar-track{grid-column:3;grid-row:1;height:8px;background:var(--c2g-theme-surface-container, rgba(0, 0, 0, .06));border-radius:4px;overflow:hidden}.c2g-gc__bar-fill{height:100%;border-radius:4px;transition:width .6s cubic-bezier(.4,0,.2,1);opacity:.85}.c2g-gc__bar-count{grid-column:4;grid-row:1;font-size:.85rem;font-weight:900;text-align:right;line-height:1}.c2g-gc__bar-names{grid-column:2/5;grid-row:2;font-size:.68rem;font-weight:500;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));padding-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n"] }]
|
|
2075
|
+
}], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }] } });
|
|
2076
|
+
|
|
2077
|
+
// Minimal hand-crafted Lottie confetti animation.
|
|
2078
|
+
// 6 colored rectangles fall from top with rotation — ~3KB, no external dependency.
|
|
2079
|
+
const CONFETTI_ANIMATION_DATA = {
|
|
2080
|
+
v: '5.7.4',
|
|
2081
|
+
fr: 30,
|
|
2082
|
+
ip: 0,
|
|
2083
|
+
op: 60,
|
|
2084
|
+
w: 200,
|
|
2085
|
+
h: 160,
|
|
2086
|
+
nm: 'confetti',
|
|
2087
|
+
ddd: 0,
|
|
2088
|
+
assets: [],
|
|
2089
|
+
layers: [
|
|
2090
|
+
makeRect(0, '#22c55e', 40, -10, 140, 150, 120, 0),
|
|
2091
|
+
makeRect(1, '#f59e0b', 80, -10, 160, 145, 90, 5),
|
|
2092
|
+
makeRect(2, '#3b82f6', 120, -10, 120, 155, 80, -5),
|
|
2093
|
+
makeRect(3, '#ef4444', 60, -10, 150, 148, 140, 10),
|
|
2094
|
+
makeRect(4, '#a855f7', 100, -10, 130, 152, 60, -8),
|
|
2095
|
+
makeRect(5, '#f97316', 140, -10, 110, 146, 100, 6),
|
|
2096
|
+
makeRect(6, '#22c55e', 30, -10, 145, 153, 110, -12),
|
|
2097
|
+
makeRect(7, '#3b82f6', 170, -10, 155, 149, 70, 15),
|
|
2098
|
+
],
|
|
2099
|
+
};
|
|
2100
|
+
function makeRect(ind, color, startX, startY, endX, endY, startRot, rotSpeed) {
|
|
2101
|
+
const hex = color.replace('#', '');
|
|
2102
|
+
const r = parseInt(hex.slice(0, 2), 16) / 255;
|
|
2103
|
+
const g = parseInt(hex.slice(2, 4), 16) / 255;
|
|
2104
|
+
const b = parseInt(hex.slice(4, 6), 16) / 255;
|
|
2105
|
+
return {
|
|
2106
|
+
ddd: 0,
|
|
2107
|
+
ind,
|
|
2108
|
+
ty: 4,
|
|
2109
|
+
nm: `confetti-${ind}`,
|
|
2110
|
+
sr: 1,
|
|
2111
|
+
ks: {
|
|
2112
|
+
o: { a: 0, k: 100 },
|
|
2113
|
+
r: {
|
|
2114
|
+
a: 1,
|
|
2115
|
+
k: [
|
|
2116
|
+
{ t: 0, s: [startRot], e: [startRot + rotSpeed * 60], i: { x: [0.5], y: [1] }, o: { x: [0.5], y: [0] } },
|
|
2117
|
+
{ t: 60, s: [startRot + rotSpeed * 60] },
|
|
2118
|
+
],
|
|
2119
|
+
},
|
|
2120
|
+
p: {
|
|
2121
|
+
a: 1,
|
|
2122
|
+
k: [
|
|
2123
|
+
{ t: 0, s: [startX, startY, 0], e: [endX, endY, 0], i: { x: [0.5], y: [1] }, o: { x: [0.5], y: [0] } },
|
|
2124
|
+
{ t: 60, s: [endX, endY, 0] },
|
|
2125
|
+
],
|
|
2126
|
+
},
|
|
2127
|
+
a: { a: 0, k: [0, 0, 0] },
|
|
2128
|
+
s: { a: 0, k: [100, 100, 100] },
|
|
2129
|
+
},
|
|
2130
|
+
ao: 0,
|
|
2131
|
+
shapes: [
|
|
2132
|
+
{
|
|
2133
|
+
ty: 'rc',
|
|
2134
|
+
d: 1,
|
|
2135
|
+
s: { a: 0, k: [8, 8] },
|
|
2136
|
+
p: { a: 0, k: [0, 0] },
|
|
2137
|
+
r: { a: 0, k: 1 },
|
|
2138
|
+
nm: 'rect',
|
|
2139
|
+
},
|
|
2140
|
+
{
|
|
2141
|
+
ty: 'fl',
|
|
2142
|
+
c: { a: 0, k: [r, g, b, 1] },
|
|
2143
|
+
o: { a: 0, k: 100 },
|
|
2144
|
+
r: 1,
|
|
2145
|
+
nm: 'fill',
|
|
2146
|
+
},
|
|
2147
|
+
],
|
|
2148
|
+
ip: 0,
|
|
2149
|
+
op: 60,
|
|
2150
|
+
st: 0,
|
|
2151
|
+
bm: 0,
|
|
2152
|
+
};
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
class CriticalItemsAlertWidgetComponent {
|
|
2156
|
+
data = input.required(...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
2157
|
+
daysUntil = computed(() => {
|
|
2158
|
+
const from = new Date(this.data().tourFromDate);
|
|
2159
|
+
from.setHours(0, 0, 0, 0);
|
|
2160
|
+
const today = new Date();
|
|
2161
|
+
today.setHours(0, 0, 0, 0);
|
|
2162
|
+
return Math.max(0, Math.ceil((from.getTime() - today.getTime()) / 86400000));
|
|
2163
|
+
}, ...(ngDevMode ? [{ debugName: "daysUntil" }] : []));
|
|
2164
|
+
urgency = computed(() => {
|
|
2165
|
+
const open = this.data().openItems.length;
|
|
2166
|
+
const days = this.daysUntil();
|
|
2167
|
+
if (open === 0)
|
|
2168
|
+
return 'ok';
|
|
2169
|
+
if (days <= 2 || (days <= 5 && open > 3))
|
|
2170
|
+
return 'critical';
|
|
2171
|
+
return 'warning';
|
|
2172
|
+
}, ...(ngDevMode ? [{ debugName: "urgency" }] : []));
|
|
2173
|
+
urgencyLabel = computed(() => {
|
|
2174
|
+
const d = this.daysUntil();
|
|
2175
|
+
if (d === 0)
|
|
2176
|
+
return 'Heute gehts los!';
|
|
2177
|
+
if (d === 1)
|
|
2178
|
+
return 'Morgen gehts los!';
|
|
2179
|
+
return `Noch ${d} Tage`;
|
|
2180
|
+
}, ...(ngDevMode ? [{ debugName: "urgencyLabel" }] : []));
|
|
2181
|
+
allClear = computed(() => this.data().openItems.length === 0, ...(ngDevMode ? [{ debugName: "allClear" }] : []));
|
|
2182
|
+
personalItems = computed(() => this.data().openItems.filter(i => i.scope === 'personal').slice(0, 4), ...(ngDevMode ? [{ debugName: "personalItems" }] : []));
|
|
2183
|
+
sharedItems = computed(() => this.data().openItems.filter(i => i.scope === 'shared').slice(0, 4), ...(ngDevMode ? [{ debugName: "sharedItems" }] : []));
|
|
2184
|
+
personalHidden = computed(() => Math.max(0, this.data().openItems.filter(i => i.scope === 'personal').length - 4), ...(ngDevMode ? [{ debugName: "personalHidden" }] : []));
|
|
2185
|
+
sharedHidden = computed(() => Math.max(0, this.data().openItems.filter(i => i.scope === 'shared').length - 4), ...(ngDevMode ? [{ debugName: "sharedHidden" }] : []));
|
|
2186
|
+
// Resolved visual type for the all-clear state
|
|
2187
|
+
successVisualType = computed(() => this.data().successVisual?.type ?? 'lottie', ...(ngDevMode ? [{ debugName: "successVisualType" }] : []));
|
|
2188
|
+
lottieOptions = computed(() => {
|
|
2189
|
+
const v = this.data().successVisual;
|
|
2190
|
+
return {
|
|
2191
|
+
animationData: v?.type === 'lottie' ? v.value : CONFETTI_ANIMATION_DATA,
|
|
2192
|
+
loop: false,
|
|
2193
|
+
autoplay: true,
|
|
2194
|
+
};
|
|
2195
|
+
}, ...(ngDevMode ? [{ debugName: "lottieOptions" }] : []));
|
|
2196
|
+
imageValue = computed(() => {
|
|
2197
|
+
const v = this.data().successVisual;
|
|
2198
|
+
return v?.type === 'image' ? v.value : null;
|
|
2199
|
+
}, ...(ngDevMode ? [{ debugName: "imageValue" }] : []));
|
|
2200
|
+
imageAlt = computed(() => this.data().successVisual?.alt ?? 'Erfolg', ...(ngDevMode ? [{ debugName: "imageAlt" }] : []));
|
|
2201
|
+
iconValue = computed(() => {
|
|
2202
|
+
const v = this.data().successVisual;
|
|
2203
|
+
return v?.type === 'icon' ? v.value : '🎉';
|
|
2204
|
+
}, ...(ngDevMode ? [{ debugName: "iconValue" }] : []));
|
|
2205
|
+
sharedProgress(item) {
|
|
2206
|
+
if (!item.neededCount)
|
|
2207
|
+
return 0;
|
|
2208
|
+
return Math.min((item.coveredCount ?? 0) / item.neededCount, 1) * 100;
|
|
2209
|
+
}
|
|
2210
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: CriticalItemsAlertWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2211
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: CriticalItemsAlertWidgetComponent, isStandalone: true, selector: "c2g-critical-items-alert-widget", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"c2g-cia\" [class]=\"'c2g-cia--' + urgency()\">\n\n <div class=\"c2g-cia__header\">\n <div class=\"c2g-cia__title-col\">\n <span class=\"c2g-cia__label\">\n @if (urgency() === 'critical') { \uD83D\uDEA8 }\n @else if (urgency() === 'warning') { \u26A0\uFE0F }\n @else { \u2705 }\n Kritische Items\n </span>\n @if (data().tourName) {\n <span class=\"c2g-cia__tour\">{{ data().tourName }}</span>\n }\n </div>\n <div class=\"c2g-cia__countdown\">\n <span class=\"c2g-cia__countdown-number\">{{ daysUntil() }}</span>\n <span class=\"c2g-cia__countdown-unit\">{{ daysUntil() === 1 ? 'Tag' : 'Tage' }}</span>\n </div>\n </div>\n\n @if (allClear()) {\n <div class=\"c2g-cia__all-clear\">\n\n <!-- Visual slot -->\n <div class=\"c2g-cia__success-visual\" aria-hidden=\"true\">\n @switch (successVisualType()) {\n @case ('lottie') {\n <ng-lottie [options]=\"lottieOptions()\" width=\"100px\" height=\"80px\" />\n }\n @case ('image') {\n <img class=\"c2g-cia__success-img\"\n [src]=\"imageValue()\"\n [alt]=\"imageAlt()\" />\n }\n @case ('icon') {\n <span class=\"c2g-cia__success-icon\">{{ iconValue() }}</span>\n }\n }\n </div>\n\n <div class=\"c2g-cia__all-clear-text-block\">\n <span class=\"c2g-cia__all-clear-headline\">Alles dabei! \uD83C\uDF89</span>\n <span class=\"c2g-cia__all-clear-sub\">Alle kritischen Items sind gepackt \u2014 bereit f\u00FCr das Abenteuer.</span>\n </div>\n </div>\n } @else {\n\n <!-- Progress summary -->\n <div class=\"c2g-cia__summary\">\n <span class=\"c2g-cia__open-count\">{{ data().openItems.length }}</span>\n <span class=\"c2g-cia__summary-text\">\n von {{ data().totalCritical }} kritischen\n {{ data().totalCritical === 1 ? 'Item fehlt' : 'Items fehlen' }}\n </span>\n </div>\n <div class=\"c2g-cia__progress-track\">\n <div class=\"c2g-cia__progress-fill\"\n [style.width.%]=\"((data().totalCritical - data().openItems.length) / data().totalCritical) * 100\">\n </div>\n </div>\n\n <!-- Side-by-side sections -->\n <div class=\"c2g-cia__sections\">\n\n <!-- Personal items -->\n @if (personalItems().length > 0) {\n <div class=\"c2g-cia__section c2g-cia__section--personal\">\n <div class=\"c2g-cia__section-header\">\n <span class=\"c2g-cia__section-icon c2g-cia__section-icon--personal\">\uD83D\uDC64</span>\n <div class=\"c2g-cia__section-titles\">\n <span class=\"c2g-cia__section-title\">Pers\u00F6nlich</span>\n <span class=\"c2g-cia__section-hint\">Jeder braucht es selbst</span>\n </div>\n </div>\n <ul class=\"c2g-cia__list\">\n @for (item of personalItems(); track item.name) {\n <li class=\"c2g-cia__item c2g-cia__item--personal\">\n <span class=\"c2g-cia__item-dot\"></span>\n <span class=\"c2g-cia__item-name\">{{ item.name }}</span>\n @if (item.missingFor?.length) {\n <span class=\"c2g-cia__item-missing\">{{ item.missingFor!.join(', ') }}</span>\n }\n </li>\n }\n @if (personalHidden() > 0) {\n <li class=\"c2g-cia__item c2g-cia__item--more\">+ {{ personalHidden() }} weitere</li>\n }\n </ul>\n </div>\n }\n\n <!-- Shared items -->\n @if (sharedItems().length > 0) {\n <div class=\"c2g-cia__section c2g-cia__section--shared\">\n <div class=\"c2g-cia__section-header\">\n <span class=\"c2g-cia__section-icon c2g-cia__section-icon--shared\">\uD83D\uDC65</span>\n <div class=\"c2g-cia__section-titles\">\n <span class=\"c2g-cia__section-title\">Geteilt</span>\n <span class=\"c2g-cia__section-hint\">Mindestmenge muss gedeckt sein</span>\n </div>\n </div>\n <ul class=\"c2g-cia__list\">\n @for (item of sharedItems(); track item.name) {\n <li class=\"c2g-cia__item c2g-cia__item--shared\">\n <div class=\"c2g-cia__shared-top\">\n <span class=\"c2g-cia__item-dot\"></span>\n <span class=\"c2g-cia__item-name\">{{ item.name }}</span>\n @if (item.neededCount) {\n <span class=\"c2g-cia__shared-ratio\">\n {{ item.coveredCount ?? 0 }}/{{ item.neededCount }}\n </span>\n }\n </div>\n @if (item.neededCount && item.neededCount > 1) {\n <div class=\"c2g-cia__shared-bar-track\">\n <div class=\"c2g-cia__shared-bar-fill\" [style.width.%]=\"sharedProgress(item)\"></div>\n </div>\n }\n @if (item.coveredBy?.length) {\n <span class=\"c2g-cia__shared-covered\">\u2713 {{ item.coveredBy!.join(', ') }}</span>\n }\n </li>\n }\n @if (sharedHidden() > 0) {\n <li class=\"c2g-cia__item c2g-cia__item--more\">+ {{ sharedHidden() }} weitere</li>\n }\n </ul>\n </div>\n }\n\n </div>\n }\n\n <div class=\"c2g-cia__footer\">{{ urgencyLabel() }}</div>\n\n</div>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-cia{border-radius:var(--c2g-radius-xl, 20px);background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1.5px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));overflow:hidden;display:flex;flex-direction:column;transition:box-shadow .2s ease,transform .2s ease}.c2g-cia:hover{box-shadow:0 8px 28px #0000001a;transform:translateY(-2px)}.c2g-cia--ok{--cia-accent: #22c55e;--cia-bg: rgba(34, 197, 94, .06);border-color:#22c55e4d}.c2g-cia--warning{--cia-accent: #f59e0b;--cia-bg: rgba(245, 158, 11, .06);border-color:#f59e0b59}.c2g-cia--critical{--cia-accent: #ef4444;--cia-bg: rgba(239, 68, 68, .06);border-color:#ef444466;animation:c2g-cia-pulse 2s ease-in-out infinite}@keyframes c2g-cia-pulse{0%,to{box-shadow:none}50%{box-shadow:0 0 0 4px #ef444426}}.c2g-cia__header{display:flex;align-items:flex-start;justify-content:space-between;padding:16px 18px 12px;gap:10px;background:var(--cia-bg)}.c2g-cia__title-col{display:flex;flex-direction:column;gap:2px}.c2g-cia__label{font-size:.8rem;font-weight:700;color:var(--cia-accent)}.c2g-cia__tour{font-size:.82rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c2g-cia__countdown{display:flex;flex-direction:column;align-items:center;background:var(--cia-accent);border-radius:12px;padding:6px 12px;flex-shrink:0}.c2g-cia__countdown-number{font-size:1.6rem;font-weight:900;line-height:1;color:#fff}.c2g-cia__countdown-unit{font-size:.62rem;font-weight:700;color:#fffc;text-transform:uppercase;letter-spacing:.06em}.c2g-cia__all-clear{display:flex;align-items:center;gap:12px;padding:16px 18px 18px}.c2g-cia__success-visual{flex-shrink:0;width:100px;height:80px;overflow:hidden;display:flex;align-items:center;justify-content:center}.c2g-cia__success-img{width:100%;height:100%;object-fit:contain;border-radius:8px}.c2g-cia__success-icon{font-size:3.5rem;line-height:1;animation:c2g-icon-pop .6s cubic-bezier(.34,1.56,.64,1) both}@keyframes c2g-icon-pop{0%{transform:scale(0) rotate(-20deg);opacity:0}70%{transform:scale(1.15) rotate(8deg);opacity:1}to{transform:scale(1) rotate(0);opacity:1}}.c2g-cia__all-clear-text-block{display:flex;flex-direction:column;gap:4px}.c2g-cia__all-clear-headline{font-size:1.2rem;font-weight:900;color:#22c55e;letter-spacing:-.02em;line-height:1}.c2g-cia__all-clear-sub{font-size:.78rem;font-weight:600;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));line-height:1.3}.c2g-cia__summary{display:flex;align-items:baseline;gap:6px;padding:12px 18px 0}.c2g-cia__open-count{font-size:2rem;font-weight:900;line-height:1;color:var(--cia-accent)}.c2g-cia__summary-text{font-size:.82rem;font-weight:600;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-cia__progress-track{height:4px;background:var(--c2g-theme-outline-variant, rgba(0, 0, 0, .08));margin:8px 18px 0;border-radius:2px;overflow:hidden}.c2g-cia__progress-fill{height:100%;background:var(--cia-accent);border-radius:2px;transition:width .7s cubic-bezier(.4,0,.2,1)}.c2g-cia__sections{display:grid;grid-template-columns:1fr;gap:0;margin-top:10px}@media(min-width:480px){.c2g-cia__sections{grid-template-columns:1fr 1fr;gap:0}}.c2g-cia__section{padding:0 18px 4px}.c2g-cia__section+.c2g-cia__section{padding-top:10px;border-top:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant))}@media(min-width:480px){.c2g-cia__section+.c2g-cia__section{padding-top:0;border-top:none;border-left:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant))}}.c2g-cia__section--personal{--sec-accent: #f97316}.c2g-cia__section--shared{--sec-accent: #3b82f6}.c2g-cia__section-header{display:flex;align-items:flex-start;gap:6px;margin-bottom:7px}.c2g-cia__section-titles{display:flex;flex-direction:column;gap:1px}.c2g-cia__section-icon{font-size:.85rem;line-height:1;width:22px;height:22px;border-radius:6px;display:flex;align-items:center;justify-content:center;flex-shrink:0}.c2g-cia__section-icon--personal{background:#f973161f}.c2g-cia__section-icon--shared{background:#3b82f61f}.c2g-cia__section-title{font-size:.72rem;font-weight:800;text-transform:uppercase;letter-spacing:.08em;color:var(--sec-accent, var(--c2g-theme-on-surface, var(--c2g-color-text-primary)))}.c2g-cia__section-hint{font-size:.62rem;font-weight:500;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-cia__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:5px}.c2g-cia__item{display:flex;align-items:center;gap:7px;font-size:.82rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}.c2g-cia__item--personal .c2g-cia__item-dot{background:#f97316}.c2g-cia__item--shared .c2g-cia__item-dot{background:#3b82f6}.c2g-cia__item--shared{flex-direction:column;align-items:stretch;gap:3px}.c2g-cia__item--more{font-size:.72rem;font-weight:600;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-cia__item-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0;background:var(--cia-accent)}.c2g-cia__item-name{flex:1}.c2g-cia__item-missing{font-size:.68rem;font-weight:500;color:#f97316;background:#f973161a;border-radius:4px;padding:1px 5px;white-space:nowrap}.c2g-cia__shared-top{display:flex;align-items:center;gap:7px}.c2g-cia__shared-ratio{font-size:.72rem;font-weight:800;color:#3b82f6;background:#3b82f61a;border-radius:4px;padding:1px 6px;white-space:nowrap;margin-left:auto}.c2g-cia__shared-bar-track{height:3px;background:#3b82f626;border-radius:2px;overflow:hidden;margin-left:13px}.c2g-cia__shared-bar-fill{height:100%;background:#3b82f6;border-radius:2px;transition:width .6s cubic-bezier(.4,0,.2,1)}.c2g-cia__shared-covered{font-size:.68rem;font-weight:500;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));margin-left:13px}.c2g-cia__footer{margin-top:12px;padding:8px 18px;font-size:.72rem;font-weight:700;text-align:center;color:var(--cia-accent);background:var(--cia-bg);border-top:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant))}\n"], dependencies: [{ kind: "component", type: LottieComponent, selector: "ng-lottie", inputs: ["width", "height"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2212
|
+
}
|
|
2213
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: CriticalItemsAlertWidgetComponent, decorators: [{
|
|
2214
|
+
type: Component,
|
|
2215
|
+
args: [{ selector: 'c2g-critical-items-alert-widget', standalone: true, imports: [LottieComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"c2g-cia\" [class]=\"'c2g-cia--' + urgency()\">\n\n <div class=\"c2g-cia__header\">\n <div class=\"c2g-cia__title-col\">\n <span class=\"c2g-cia__label\">\n @if (urgency() === 'critical') { \uD83D\uDEA8 }\n @else if (urgency() === 'warning') { \u26A0\uFE0F }\n @else { \u2705 }\n Kritische Items\n </span>\n @if (data().tourName) {\n <span class=\"c2g-cia__tour\">{{ data().tourName }}</span>\n }\n </div>\n <div class=\"c2g-cia__countdown\">\n <span class=\"c2g-cia__countdown-number\">{{ daysUntil() }}</span>\n <span class=\"c2g-cia__countdown-unit\">{{ daysUntil() === 1 ? 'Tag' : 'Tage' }}</span>\n </div>\n </div>\n\n @if (allClear()) {\n <div class=\"c2g-cia__all-clear\">\n\n <!-- Visual slot -->\n <div class=\"c2g-cia__success-visual\" aria-hidden=\"true\">\n @switch (successVisualType()) {\n @case ('lottie') {\n <ng-lottie [options]=\"lottieOptions()\" width=\"100px\" height=\"80px\" />\n }\n @case ('image') {\n <img class=\"c2g-cia__success-img\"\n [src]=\"imageValue()\"\n [alt]=\"imageAlt()\" />\n }\n @case ('icon') {\n <span class=\"c2g-cia__success-icon\">{{ iconValue() }}</span>\n }\n }\n </div>\n\n <div class=\"c2g-cia__all-clear-text-block\">\n <span class=\"c2g-cia__all-clear-headline\">Alles dabei! \uD83C\uDF89</span>\n <span class=\"c2g-cia__all-clear-sub\">Alle kritischen Items sind gepackt \u2014 bereit f\u00FCr das Abenteuer.</span>\n </div>\n </div>\n } @else {\n\n <!-- Progress summary -->\n <div class=\"c2g-cia__summary\">\n <span class=\"c2g-cia__open-count\">{{ data().openItems.length }}</span>\n <span class=\"c2g-cia__summary-text\">\n von {{ data().totalCritical }} kritischen\n {{ data().totalCritical === 1 ? 'Item fehlt' : 'Items fehlen' }}\n </span>\n </div>\n <div class=\"c2g-cia__progress-track\">\n <div class=\"c2g-cia__progress-fill\"\n [style.width.%]=\"((data().totalCritical - data().openItems.length) / data().totalCritical) * 100\">\n </div>\n </div>\n\n <!-- Side-by-side sections -->\n <div class=\"c2g-cia__sections\">\n\n <!-- Personal items -->\n @if (personalItems().length > 0) {\n <div class=\"c2g-cia__section c2g-cia__section--personal\">\n <div class=\"c2g-cia__section-header\">\n <span class=\"c2g-cia__section-icon c2g-cia__section-icon--personal\">\uD83D\uDC64</span>\n <div class=\"c2g-cia__section-titles\">\n <span class=\"c2g-cia__section-title\">Pers\u00F6nlich</span>\n <span class=\"c2g-cia__section-hint\">Jeder braucht es selbst</span>\n </div>\n </div>\n <ul class=\"c2g-cia__list\">\n @for (item of personalItems(); track item.name) {\n <li class=\"c2g-cia__item c2g-cia__item--personal\">\n <span class=\"c2g-cia__item-dot\"></span>\n <span class=\"c2g-cia__item-name\">{{ item.name }}</span>\n @if (item.missingFor?.length) {\n <span class=\"c2g-cia__item-missing\">{{ item.missingFor!.join(', ') }}</span>\n }\n </li>\n }\n @if (personalHidden() > 0) {\n <li class=\"c2g-cia__item c2g-cia__item--more\">+ {{ personalHidden() }} weitere</li>\n }\n </ul>\n </div>\n }\n\n <!-- Shared items -->\n @if (sharedItems().length > 0) {\n <div class=\"c2g-cia__section c2g-cia__section--shared\">\n <div class=\"c2g-cia__section-header\">\n <span class=\"c2g-cia__section-icon c2g-cia__section-icon--shared\">\uD83D\uDC65</span>\n <div class=\"c2g-cia__section-titles\">\n <span class=\"c2g-cia__section-title\">Geteilt</span>\n <span class=\"c2g-cia__section-hint\">Mindestmenge muss gedeckt sein</span>\n </div>\n </div>\n <ul class=\"c2g-cia__list\">\n @for (item of sharedItems(); track item.name) {\n <li class=\"c2g-cia__item c2g-cia__item--shared\">\n <div class=\"c2g-cia__shared-top\">\n <span class=\"c2g-cia__item-dot\"></span>\n <span class=\"c2g-cia__item-name\">{{ item.name }}</span>\n @if (item.neededCount) {\n <span class=\"c2g-cia__shared-ratio\">\n {{ item.coveredCount ?? 0 }}/{{ item.neededCount }}\n </span>\n }\n </div>\n @if (item.neededCount && item.neededCount > 1) {\n <div class=\"c2g-cia__shared-bar-track\">\n <div class=\"c2g-cia__shared-bar-fill\" [style.width.%]=\"sharedProgress(item)\"></div>\n </div>\n }\n @if (item.coveredBy?.length) {\n <span class=\"c2g-cia__shared-covered\">\u2713 {{ item.coveredBy!.join(', ') }}</span>\n }\n </li>\n }\n @if (sharedHidden() > 0) {\n <li class=\"c2g-cia__item c2g-cia__item--more\">+ {{ sharedHidden() }} weitere</li>\n }\n </ul>\n </div>\n }\n\n </div>\n }\n\n <div class=\"c2g-cia__footer\">{{ urgencyLabel() }}</div>\n\n</div>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-cia{border-radius:var(--c2g-radius-xl, 20px);background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1.5px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));overflow:hidden;display:flex;flex-direction:column;transition:box-shadow .2s ease,transform .2s ease}.c2g-cia:hover{box-shadow:0 8px 28px #0000001a;transform:translateY(-2px)}.c2g-cia--ok{--cia-accent: #22c55e;--cia-bg: rgba(34, 197, 94, .06);border-color:#22c55e4d}.c2g-cia--warning{--cia-accent: #f59e0b;--cia-bg: rgba(245, 158, 11, .06);border-color:#f59e0b59}.c2g-cia--critical{--cia-accent: #ef4444;--cia-bg: rgba(239, 68, 68, .06);border-color:#ef444466;animation:c2g-cia-pulse 2s ease-in-out infinite}@keyframes c2g-cia-pulse{0%,to{box-shadow:none}50%{box-shadow:0 0 0 4px #ef444426}}.c2g-cia__header{display:flex;align-items:flex-start;justify-content:space-between;padding:16px 18px 12px;gap:10px;background:var(--cia-bg)}.c2g-cia__title-col{display:flex;flex-direction:column;gap:2px}.c2g-cia__label{font-size:.8rem;font-weight:700;color:var(--cia-accent)}.c2g-cia__tour{font-size:.82rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c2g-cia__countdown{display:flex;flex-direction:column;align-items:center;background:var(--cia-accent);border-radius:12px;padding:6px 12px;flex-shrink:0}.c2g-cia__countdown-number{font-size:1.6rem;font-weight:900;line-height:1;color:#fff}.c2g-cia__countdown-unit{font-size:.62rem;font-weight:700;color:#fffc;text-transform:uppercase;letter-spacing:.06em}.c2g-cia__all-clear{display:flex;align-items:center;gap:12px;padding:16px 18px 18px}.c2g-cia__success-visual{flex-shrink:0;width:100px;height:80px;overflow:hidden;display:flex;align-items:center;justify-content:center}.c2g-cia__success-img{width:100%;height:100%;object-fit:contain;border-radius:8px}.c2g-cia__success-icon{font-size:3.5rem;line-height:1;animation:c2g-icon-pop .6s cubic-bezier(.34,1.56,.64,1) both}@keyframes c2g-icon-pop{0%{transform:scale(0) rotate(-20deg);opacity:0}70%{transform:scale(1.15) rotate(8deg);opacity:1}to{transform:scale(1) rotate(0);opacity:1}}.c2g-cia__all-clear-text-block{display:flex;flex-direction:column;gap:4px}.c2g-cia__all-clear-headline{font-size:1.2rem;font-weight:900;color:#22c55e;letter-spacing:-.02em;line-height:1}.c2g-cia__all-clear-sub{font-size:.78rem;font-weight:600;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));line-height:1.3}.c2g-cia__summary{display:flex;align-items:baseline;gap:6px;padding:12px 18px 0}.c2g-cia__open-count{font-size:2rem;font-weight:900;line-height:1;color:var(--cia-accent)}.c2g-cia__summary-text{font-size:.82rem;font-weight:600;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-cia__progress-track{height:4px;background:var(--c2g-theme-outline-variant, rgba(0, 0, 0, .08));margin:8px 18px 0;border-radius:2px;overflow:hidden}.c2g-cia__progress-fill{height:100%;background:var(--cia-accent);border-radius:2px;transition:width .7s cubic-bezier(.4,0,.2,1)}.c2g-cia__sections{display:grid;grid-template-columns:1fr;gap:0;margin-top:10px}@media(min-width:480px){.c2g-cia__sections{grid-template-columns:1fr 1fr;gap:0}}.c2g-cia__section{padding:0 18px 4px}.c2g-cia__section+.c2g-cia__section{padding-top:10px;border-top:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant))}@media(min-width:480px){.c2g-cia__section+.c2g-cia__section{padding-top:0;border-top:none;border-left:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant))}}.c2g-cia__section--personal{--sec-accent: #f97316}.c2g-cia__section--shared{--sec-accent: #3b82f6}.c2g-cia__section-header{display:flex;align-items:flex-start;gap:6px;margin-bottom:7px}.c2g-cia__section-titles{display:flex;flex-direction:column;gap:1px}.c2g-cia__section-icon{font-size:.85rem;line-height:1;width:22px;height:22px;border-radius:6px;display:flex;align-items:center;justify-content:center;flex-shrink:0}.c2g-cia__section-icon--personal{background:#f973161f}.c2g-cia__section-icon--shared{background:#3b82f61f}.c2g-cia__section-title{font-size:.72rem;font-weight:800;text-transform:uppercase;letter-spacing:.08em;color:var(--sec-accent, var(--c2g-theme-on-surface, var(--c2g-color-text-primary)))}.c2g-cia__section-hint{font-size:.62rem;font-weight:500;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-cia__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:5px}.c2g-cia__item{display:flex;align-items:center;gap:7px;font-size:.82rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}.c2g-cia__item--personal .c2g-cia__item-dot{background:#f97316}.c2g-cia__item--shared .c2g-cia__item-dot{background:#3b82f6}.c2g-cia__item--shared{flex-direction:column;align-items:stretch;gap:3px}.c2g-cia__item--more{font-size:.72rem;font-weight:600;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-cia__item-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0;background:var(--cia-accent)}.c2g-cia__item-name{flex:1}.c2g-cia__item-missing{font-size:.68rem;font-weight:500;color:#f97316;background:#f973161a;border-radius:4px;padding:1px 5px;white-space:nowrap}.c2g-cia__shared-top{display:flex;align-items:center;gap:7px}.c2g-cia__shared-ratio{font-size:.72rem;font-weight:800;color:#3b82f6;background:#3b82f61a;border-radius:4px;padding:1px 6px;white-space:nowrap;margin-left:auto}.c2g-cia__shared-bar-track{height:3px;background:#3b82f626;border-radius:2px;overflow:hidden;margin-left:13px}.c2g-cia__shared-bar-fill{height:100%;background:#3b82f6;border-radius:2px;transition:width .6s cubic-bezier(.4,0,.2,1)}.c2g-cia__shared-covered{font-size:.68rem;font-weight:500;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));margin-left:13px}.c2g-cia__footer{margin-top:12px;padding:8px 18px;font-size:.72rem;font-weight:700;text-align:center;color:var(--cia-accent);background:var(--cia-bg);border-top:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant))}\n"] }]
|
|
2216
|
+
}], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }] } });
|
|
2217
|
+
|
|
2218
|
+
const BEAUFORT = [
|
|
2219
|
+
{ max: 1, label: 'Windstille', emoji: '🌬️', color: '#22c55e' },
|
|
2220
|
+
{ max: 5, label: 'Leichte Brise', emoji: '🌬️', color: '#84cc16' },
|
|
2221
|
+
{ max: 11, label: 'Schwache Brise', emoji: '💨', color: '#84cc16' },
|
|
2222
|
+
{ max: 19, label: 'Schwacher Wind', emoji: '💨', color: '#f59e0b' },
|
|
2223
|
+
{ max: 28, label: 'Mäßiger Wind', emoji: '💨', color: '#f59e0b' },
|
|
2224
|
+
{ max: 38, label: 'Frischer Wind', emoji: '🌀', color: '#f97316' },
|
|
2225
|
+
{ max: 49, label: 'Starker Wind', emoji: '🌀', color: '#f97316' },
|
|
2226
|
+
{ max: 61, label: 'Stürmisch', emoji: '⛈️', color: '#ef4444' },
|
|
2227
|
+
{ max: 74, label: 'Sturm', emoji: '⛈️', color: '#ef4444' },
|
|
2228
|
+
{ max: 88, label: 'Schwerer Sturm', emoji: '🌪️', color: '#dc2626' },
|
|
2229
|
+
{ max: 102, label: 'Orkan', emoji: '🌪️', color: '#dc2626' },
|
|
2230
|
+
{ max: 999, label: 'Schwerer Orkan', emoji: '🌪️', color: '#dc2626' },
|
|
2231
|
+
];
|
|
2232
|
+
function beaufortFor(kmh) {
|
|
2233
|
+
return BEAUFORT.find(b => kmh <= b.max) ?? BEAUFORT[BEAUFORT.length - 1];
|
|
2234
|
+
}
|
|
2235
|
+
function directionLabel(deg) {
|
|
2236
|
+
const dirs = ['N', 'NO', 'O', 'SO', 'S', 'SW', 'W', 'NW'];
|
|
2237
|
+
return dirs[Math.round(deg / 45) % 8];
|
|
2238
|
+
}
|
|
2239
|
+
class WindIndicatorWidgetComponent {
|
|
2240
|
+
data = input.required(...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
2241
|
+
days = computed(() => this.data().days.slice(0, 7), ...(ngDevMode ? [{ debugName: "days" }] : []));
|
|
2242
|
+
peak = computed(() => this.days().reduce((max, d) => d.windSpeed > max.windSpeed ? d : max, this.days()[0]), ...(ngDevMode ? [{ debugName: "peak" }] : []));
|
|
2243
|
+
peakBeaufort = computed(() => beaufortFor(this.peak()?.windSpeed ?? 0), ...(ngDevMode ? [{ debugName: "peakBeaufort" }] : []));
|
|
2244
|
+
overallRisk = computed(() => {
|
|
2245
|
+
const avg = this.days().reduce((s, d) => s + d.windSpeed, 0) / (this.days().length || 1);
|
|
2246
|
+
if (avg >= 50)
|
|
2247
|
+
return 'stormy';
|
|
2248
|
+
if (avg >= 28)
|
|
2249
|
+
return 'windy';
|
|
2250
|
+
if (avg >= 11)
|
|
2251
|
+
return 'breezy';
|
|
2252
|
+
return 'calm';
|
|
2253
|
+
}, ...(ngDevMode ? [{ debugName: "overallRisk" }] : []));
|
|
2254
|
+
enriched = computed(() => this.days().map(d => ({
|
|
2255
|
+
...d,
|
|
2256
|
+
bf: beaufortFor(d.windSpeed),
|
|
2257
|
+
dirLabel: directionLabel(d.windDirection),
|
|
2258
|
+
// Arrow rotation: wind direction = where it comes FROM → arrow points that way
|
|
2259
|
+
arrowDeg: d.windDirection,
|
|
2260
|
+
barPct: Math.min(100, (d.windSpeed / 100) * 100),
|
|
2261
|
+
})), ...(ngDevMode ? [{ debugName: "enriched" }] : []));
|
|
2262
|
+
formatDate(iso) {
|
|
2263
|
+
return new Date(iso).toLocaleDateString('de-DE', { weekday: 'short', day: 'numeric' });
|
|
2264
|
+
}
|
|
2265
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: WindIndicatorWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2266
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: WindIndicatorWidgetComponent, isStandalone: true, selector: "c2g-wind-indicator-widget", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<article class=\"c2g-wind\" [class]=\"'c2g-wind--' + overallRisk()\">\n\n <header class=\"c2g-wind__header\">\n <div class=\"c2g-wind__title-col\">\n <span class=\"c2g-wind__label\">\uD83D\uDCA8 Wind</span>\n @if (data().tourName) {\n <span class=\"c2g-wind__tour\">{{ data().tourName }}</span>\n }\n </div>\n @if (peak()) {\n <div class=\"c2g-wind__peak-badge\">\n <span class=\"c2g-wind__peak-emoji\">{{ peakBeaufort().emoji }}</span>\n <div class=\"c2g-wind__peak-text\">\n <span class=\"c2g-wind__peak-speed\">{{ peak().windSpeed }}<small>km/h</small></span>\n <span class=\"c2g-wind__peak-sublabel\">Spitze</span>\n </div>\n </div>\n }\n </header>\n\n <div class=\"c2g-wind__days\">\n @for (day of enriched(); track day.date) {\n <div class=\"c2g-wind__day\">\n\n <!-- Compass rose -->\n <svg class=\"c2g-wind__compass\" viewBox=\"0 0 40 40\" aria-hidden=\"true\">\n <circle cx=\"20\" cy=\"20\" r=\"18\" class=\"c2g-wind__compass-ring\"/>\n <text class=\"c2g-wind__compass-n\" x=\"20\" y=\"7\">N</text>\n <g [style.transform-origin]=\"'20px 20px'\" [style.transform]=\"'rotate(' + day.arrowDeg + 'deg)'\">\n <polygon points=\"20,4 23,18 20,16 17,18\" [attr.fill]=\"day.bf.color\"/>\n <polygon points=\"20,36 17,22 20,24 23,22\" fill=\"rgba(255,255,255,0.18)\"/>\n </g>\n <circle cx=\"20\" cy=\"20\" r=\"2.5\" [attr.fill]=\"day.bf.color\"/>\n </svg>\n\n <!-- Date -->\n <span class=\"c2g-wind__date\">{{ formatDate(day.date) }}</span>\n\n <!-- Speed bar -->\n <div class=\"c2g-wind__bar-track\">\n <div class=\"c2g-wind__bar-fill\"\n [style.width.%]=\"day.barPct\"\n [style.background]=\"day.bf.color\">\n </div>\n </div>\n\n <!-- Speed value -->\n <span class=\"c2g-wind__speed\" [style.color]=\"day.bf.color\">\n {{ day.windSpeed }}<small>km/h</small>\n </span>\n\n <!-- Beaufort + direction -->\n <span class=\"c2g-wind__meta\">{{ day.dirLabel }} \u00B7 {{ day.bf.label }}</span>\n\n </div>\n }\n </div>\n\n</article>\n", styles: [":host{display:block}.c2g-wind{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));border-radius:var(--c2g-radius-xl, 20px);padding:1.25rem 1.5rem 1rem;display:flex;flex-direction:column;gap:1rem;position:relative;overflow:hidden;transition:box-shadow .2s ease,transform .2s ease}.c2g-wind:hover{transform:translateY(-2px);box-shadow:0 8px 32px #0000003d}.c2g-wind:before{content:\"\";position:absolute;inset:0;background:radial-gradient(ellipse 80% 50% at 100% 0%,rgba(148,163,184,.04) 0%,transparent 70%);pointer-events:none}.c2g-wind--stormy:before{background:radial-gradient(ellipse 80% 50% at 100% 0%,rgba(239,68,68,.06) 0%,transparent 70%)}.c2g-wind--windy:before{background:radial-gradient(ellipse 80% 50% at 100% 0%,rgba(249,115,22,.06) 0%,transparent 70%)}.c2g-wind__header{display:flex;align-items:flex-start;justify-content:space-between;gap:.75rem}.c2g-wind__title-col{display:flex;flex-direction:column;gap:.125rem}.c2g-wind__label{font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-secondary))}.c2g-wind__tour{font-size:.8125rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c2g-wind__peak-badge{display:flex;align-items:center;gap:.5rem;background:var(--c2g-theme-surface-container-low, var(--c2g-color-bg-base));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));border-radius:12px;padding:.375rem .75rem;flex-shrink:0}.c2g-wind__peak-emoji{font-size:1.25rem;line-height:1}.c2g-wind__peak-text{display:flex;flex-direction:column;gap:0}.c2g-wind__peak-speed{font-size:.9375rem;font-weight:900;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));font-variant-numeric:tabular-nums;line-height:1.1}.c2g-wind__peak-speed small{font-size:.5625rem;font-weight:600;opacity:.55;margin-left:1px}.c2g-wind__peak-sublabel{font-size:.5625rem;font-weight:700;text-transform:uppercase;letter-spacing:.07em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-wind__days{display:flex;flex-direction:column;gap:.5rem}.c2g-wind__day{display:grid;grid-template-columns:40px 72px 1fr 56px;grid-template-rows:auto auto;column-gap:.625rem;align-items:center}.c2g-wind__compass{grid-column:1;grid-row:1/3;width:40px;height:40px;display:block}.c2g-wind__compass-ring{fill:var(--c2g-theme-surface-container, var(--c2g-color-bg-secondary));stroke:var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));stroke-width:1}.c2g-wind__compass-n{fill:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));font-size:5.5px;font-weight:800;font-family:inherit;text-anchor:middle;dominant-baseline:middle}.c2g-wind__date{grid-column:2;grid-row:1;font-size:.6875rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));white-space:nowrap}.c2g-wind__bar-track{grid-column:3;grid-row:1;height:6px;border-radius:3px;background:var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));overflow:hidden}.c2g-wind__bar-fill{height:100%;min-width:3px;border-radius:3px;transition:width .7s cubic-bezier(.4,0,.2,1)}.c2g-wind__speed{grid-column:4;grid-row:1;font-size:.875rem;font-weight:800;text-align:right;white-space:nowrap;font-variant-numeric:tabular-nums}.c2g-wind__speed small{font-size:.5625rem;font-weight:600;opacity:.6;margin-left:1px}.c2g-wind__meta{grid-column:2/5;grid-row:2;font-size:.6875rem;font-weight:600;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2267
|
+
}
|
|
2268
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: WindIndicatorWidgetComponent, decorators: [{
|
|
2269
|
+
type: Component,
|
|
2270
|
+
args: [{ selector: 'c2g-wind-indicator-widget', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<article class=\"c2g-wind\" [class]=\"'c2g-wind--' + overallRisk()\">\n\n <header class=\"c2g-wind__header\">\n <div class=\"c2g-wind__title-col\">\n <span class=\"c2g-wind__label\">\uD83D\uDCA8 Wind</span>\n @if (data().tourName) {\n <span class=\"c2g-wind__tour\">{{ data().tourName }}</span>\n }\n </div>\n @if (peak()) {\n <div class=\"c2g-wind__peak-badge\">\n <span class=\"c2g-wind__peak-emoji\">{{ peakBeaufort().emoji }}</span>\n <div class=\"c2g-wind__peak-text\">\n <span class=\"c2g-wind__peak-speed\">{{ peak().windSpeed }}<small>km/h</small></span>\n <span class=\"c2g-wind__peak-sublabel\">Spitze</span>\n </div>\n </div>\n }\n </header>\n\n <div class=\"c2g-wind__days\">\n @for (day of enriched(); track day.date) {\n <div class=\"c2g-wind__day\">\n\n <!-- Compass rose -->\n <svg class=\"c2g-wind__compass\" viewBox=\"0 0 40 40\" aria-hidden=\"true\">\n <circle cx=\"20\" cy=\"20\" r=\"18\" class=\"c2g-wind__compass-ring\"/>\n <text class=\"c2g-wind__compass-n\" x=\"20\" y=\"7\">N</text>\n <g [style.transform-origin]=\"'20px 20px'\" [style.transform]=\"'rotate(' + day.arrowDeg + 'deg)'\">\n <polygon points=\"20,4 23,18 20,16 17,18\" [attr.fill]=\"day.bf.color\"/>\n <polygon points=\"20,36 17,22 20,24 23,22\" fill=\"rgba(255,255,255,0.18)\"/>\n </g>\n <circle cx=\"20\" cy=\"20\" r=\"2.5\" [attr.fill]=\"day.bf.color\"/>\n </svg>\n\n <!-- Date -->\n <span class=\"c2g-wind__date\">{{ formatDate(day.date) }}</span>\n\n <!-- Speed bar -->\n <div class=\"c2g-wind__bar-track\">\n <div class=\"c2g-wind__bar-fill\"\n [style.width.%]=\"day.barPct\"\n [style.background]=\"day.bf.color\">\n </div>\n </div>\n\n <!-- Speed value -->\n <span class=\"c2g-wind__speed\" [style.color]=\"day.bf.color\">\n {{ day.windSpeed }}<small>km/h</small>\n </span>\n\n <!-- Beaufort + direction -->\n <span class=\"c2g-wind__meta\">{{ day.dirLabel }} \u00B7 {{ day.bf.label }}</span>\n\n </div>\n }\n </div>\n\n</article>\n", styles: [":host{display:block}.c2g-wind{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));border-radius:var(--c2g-radius-xl, 20px);padding:1.25rem 1.5rem 1rem;display:flex;flex-direction:column;gap:1rem;position:relative;overflow:hidden;transition:box-shadow .2s ease,transform .2s ease}.c2g-wind:hover{transform:translateY(-2px);box-shadow:0 8px 32px #0000003d}.c2g-wind:before{content:\"\";position:absolute;inset:0;background:radial-gradient(ellipse 80% 50% at 100% 0%,rgba(148,163,184,.04) 0%,transparent 70%);pointer-events:none}.c2g-wind--stormy:before{background:radial-gradient(ellipse 80% 50% at 100% 0%,rgba(239,68,68,.06) 0%,transparent 70%)}.c2g-wind--windy:before{background:radial-gradient(ellipse 80% 50% at 100% 0%,rgba(249,115,22,.06) 0%,transparent 70%)}.c2g-wind__header{display:flex;align-items:flex-start;justify-content:space-between;gap:.75rem}.c2g-wind__title-col{display:flex;flex-direction:column;gap:.125rem}.c2g-wind__label{font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-secondary))}.c2g-wind__tour{font-size:.8125rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c2g-wind__peak-badge{display:flex;align-items:center;gap:.5rem;background:var(--c2g-theme-surface-container-low, var(--c2g-color-bg-base));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));border-radius:12px;padding:.375rem .75rem;flex-shrink:0}.c2g-wind__peak-emoji{font-size:1.25rem;line-height:1}.c2g-wind__peak-text{display:flex;flex-direction:column;gap:0}.c2g-wind__peak-speed{font-size:.9375rem;font-weight:900;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));font-variant-numeric:tabular-nums;line-height:1.1}.c2g-wind__peak-speed small{font-size:.5625rem;font-weight:600;opacity:.55;margin-left:1px}.c2g-wind__peak-sublabel{font-size:.5625rem;font-weight:700;text-transform:uppercase;letter-spacing:.07em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-wind__days{display:flex;flex-direction:column;gap:.5rem}.c2g-wind__day{display:grid;grid-template-columns:40px 72px 1fr 56px;grid-template-rows:auto auto;column-gap:.625rem;align-items:center}.c2g-wind__compass{grid-column:1;grid-row:1/3;width:40px;height:40px;display:block}.c2g-wind__compass-ring{fill:var(--c2g-theme-surface-container, var(--c2g-color-bg-secondary));stroke:var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));stroke-width:1}.c2g-wind__compass-n{fill:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));font-size:5.5px;font-weight:800;font-family:inherit;text-anchor:middle;dominant-baseline:middle}.c2g-wind__date{grid-column:2;grid-row:1;font-size:.6875rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));white-space:nowrap}.c2g-wind__bar-track{grid-column:3;grid-row:1;height:6px;border-radius:3px;background:var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));overflow:hidden}.c2g-wind__bar-fill{height:100%;min-width:3px;border-radius:3px;transition:width .7s cubic-bezier(.4,0,.2,1)}.c2g-wind__speed{grid-column:4;grid-row:1;font-size:.875rem;font-weight:800;text-align:right;white-space:nowrap;font-variant-numeric:tabular-nums}.c2g-wind__speed small{font-size:.5625rem;font-weight:600;opacity:.6;margin-left:1px}.c2g-wind__meta{grid-column:2/5;grid-row:2;font-size:.6875rem;font-weight:600;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n"] }]
|
|
2271
|
+
}], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }] } });
|
|
2272
|
+
|
|
2273
|
+
class TotalKmWidgetComponent {
|
|
2274
|
+
data = input.required(...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
2275
|
+
displayKm = signal(0, ...(ngDevMode ? [{ debugName: "displayKm" }] : []));
|
|
2276
|
+
displayYearKm = signal(0, ...(ngDevMode ? [{ debugName: "displayYearKm" }] : []));
|
|
2277
|
+
rafId = 0;
|
|
2278
|
+
rafYearId = 0;
|
|
2279
|
+
constructor() {
|
|
2280
|
+
effect(() => {
|
|
2281
|
+
const target = this.data().totalKm;
|
|
2282
|
+
this.animateCount(target, this.displayKm, 'rafId');
|
|
2283
|
+
});
|
|
2284
|
+
effect(() => {
|
|
2285
|
+
const target = this.data().yearKm ?? 0;
|
|
2286
|
+
this.animateCount(target, this.displayYearKm, 'rafYearId');
|
|
2287
|
+
});
|
|
2288
|
+
}
|
|
2289
|
+
animateCount(target, sig, rafKey) {
|
|
2290
|
+
cancelAnimationFrame(this[rafKey]);
|
|
2291
|
+
const start = performance.now();
|
|
2292
|
+
const duration = 1400;
|
|
2293
|
+
const step = (now) => {
|
|
2294
|
+
const t = Math.min((now - start) / duration, 1);
|
|
2295
|
+
const eased = 1 - Math.pow(1 - t, 4);
|
|
2296
|
+
sig.set(Math.round(eased * target));
|
|
2297
|
+
if (t < 1)
|
|
2298
|
+
this[rafKey] = requestAnimationFrame(step);
|
|
2299
|
+
};
|
|
2300
|
+
this[rafKey] = requestAnimationFrame(step);
|
|
2301
|
+
}
|
|
2302
|
+
sparklinePath = computed(() => {
|
|
2303
|
+
const points = this.data().sparkline;
|
|
2304
|
+
if (!points || points.length < 2)
|
|
2305
|
+
return null;
|
|
2306
|
+
const W = 200, H = 48;
|
|
2307
|
+
const maxKm = Math.max(...points.map(p => p.km));
|
|
2308
|
+
const minKm = Math.min(...points.map(p => p.km));
|
|
2309
|
+
const range = maxKm - minKm || 1;
|
|
2310
|
+
const xs = points.map((_, i) => (i / (points.length - 1)) * W);
|
|
2311
|
+
const ys = points.map(p => H - ((p.km - minKm) / range) * (H - 8) - 2);
|
|
2312
|
+
let line = `M ${xs[0]} ${ys[0]}`;
|
|
2313
|
+
for (let i = 1; i < points.length; i++) {
|
|
2314
|
+
const cpx = (xs[i - 1] + xs[i]) / 2;
|
|
2315
|
+
line += ` C ${cpx} ${ys[i - 1]}, ${cpx} ${ys[i]}, ${xs[i]} ${ys[i]}`;
|
|
2316
|
+
}
|
|
2317
|
+
const area = `${line} L ${xs[xs.length - 1]} ${H} L ${xs[0]} ${H} Z`;
|
|
2318
|
+
return { line, area, lastX: xs[xs.length - 1], lastY: ys[ys.length - 1] };
|
|
2319
|
+
}, ...(ngDevMode ? [{ debugName: "sparklinePath" }] : []));
|
|
2320
|
+
ngOnDestroy() {
|
|
2321
|
+
cancelAnimationFrame(this.rafId);
|
|
2322
|
+
cancelAnimationFrame(this.rafYearId);
|
|
2323
|
+
}
|
|
2324
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: TotalKmWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2325
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: TotalKmWidgetComponent, isStandalone: true, selector: "c2g-total-km-widget", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<article class=\"c2g-tkm\">\n <header class=\"c2g-tkm__header\">\n <span class=\"c2g-tkm__icon\" aria-hidden=\"true\">\uD83D\uDDFA\uFE0F</span>\n <div class=\"c2g-tkm__titles\">\n <h3 class=\"c2g-tkm__title\">Gesamt-Kilometer</h3>\n @if (data().tourCount) {\n <span class=\"c2g-tkm__sub\">{{ data().tourCount }} Touren</span>\n }\n </div>\n </header>\n\n <div class=\"c2g-tkm__hero\">\n <span class=\"c2g-tkm__value\">{{ displayKm() | number:'1.0-0' }}</span>\n <span class=\"c2g-tkm__unit\">km</span>\n </div>\n\n @if (data().yearKm) {\n <div class=\"c2g-tkm__year-row\">\n <span class=\"c2g-tkm__year-label\">Dieses Jahr</span>\n <span class=\"c2g-tkm__year-value\">{{ displayYearKm() | number:'1.0-0' }} km</span>\n </div>\n }\n\n @if (sparklinePath()) {\n <div class=\"c2g-tkm__chart\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 200 48\" preserveAspectRatio=\"none\" class=\"c2g-tkm__svg\">\n <defs>\n <linearGradient id=\"tkm-area-grad\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stop-color=\"var(--c2g-theme-primary, #ff6b35)\" stop-opacity=\"0.35\"/>\n <stop offset=\"100%\" stop-color=\"var(--c2g-theme-primary, #ff6b35)\" stop-opacity=\"0\"/>\n </linearGradient>\n </defs>\n <path class=\"c2g-tkm__area\" [attr.d]=\"sparklinePath()!.area\" fill=\"url(#tkm-area-grad)\" />\n <path class=\"c2g-tkm__line\" [attr.d]=\"sparklinePath()!.line\" />\n <circle\n class=\"c2g-tkm__dot\"\n [attr.cx]=\"sparklinePath()!.lastX\"\n [attr.cy]=\"sparklinePath()!.lastY\"\n r=\"3\"\n />\n </svg>\n </div>\n }\n</article>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-tkm{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));border-radius:var(--c2g-radius-xl, 20px);padding:18px 20px 0;display:flex;flex-direction:column;gap:10px;position:relative;overflow:hidden;transition:box-shadow .2s ease,transform .2s ease}.c2g-tkm:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .11));transform:translateY(-2px)}.c2g-tkm:before{content:\"\";position:absolute;inset:0;background:radial-gradient(ellipse 80% 60% at 100% 0%,rgba(255,107,53,.05) 0%,transparent 70%);pointer-events:none}.c2g-tkm__header{display:flex;align-items:center;gap:8px}.c2g-tkm__icon{font-size:1.25rem;line-height:1}.c2g-tkm__titles{display:flex;flex-direction:column;gap:2px}.c2g-tkm__title{margin:0;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-tkm__sub{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));opacity:.7}.c2g-tkm__hero{display:flex;align-items:baseline;gap:5px}.c2g-tkm__value{font-size:3rem;font-weight:800;line-height:1;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));font-variant-numeric:tabular-nums;letter-spacing:-.02em}.c2g-tkm__unit{font-size:1.25rem;font-weight:700;color:var(--c2g-theme-primary, var(--c2g-color-primary))}.c2g-tkm__year-row{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:var(--c2g-theme-surface-container, var(--c2g-color-bg-secondary));border-radius:var(--c2g-radius-md, 10px)}.c2g-tkm__year-label{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-tkm__year-value{font-size:.875rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));font-variant-numeric:tabular-nums}.c2g-tkm__chart{height:52px;margin:0 -20px}.c2g-tkm__svg{width:100%;height:100%;display:block}.c2g-tkm__line{fill:none;stroke:var(--c2g-theme-primary, var(--c2g-color-primary));stroke-width:2;stroke-linecap:round;stroke-linejoin:round}.c2g-tkm__dot{fill:var(--c2g-theme-primary, var(--c2g-color-primary))}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1$2.DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2326
|
+
}
|
|
2327
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: TotalKmWidgetComponent, decorators: [{
|
|
2328
|
+
type: Component,
|
|
2329
|
+
args: [{ selector: 'c2g-total-km-widget', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<article class=\"c2g-tkm\">\n <header class=\"c2g-tkm__header\">\n <span class=\"c2g-tkm__icon\" aria-hidden=\"true\">\uD83D\uDDFA\uFE0F</span>\n <div class=\"c2g-tkm__titles\">\n <h3 class=\"c2g-tkm__title\">Gesamt-Kilometer</h3>\n @if (data().tourCount) {\n <span class=\"c2g-tkm__sub\">{{ data().tourCount }} Touren</span>\n }\n </div>\n </header>\n\n <div class=\"c2g-tkm__hero\">\n <span class=\"c2g-tkm__value\">{{ displayKm() | number:'1.0-0' }}</span>\n <span class=\"c2g-tkm__unit\">km</span>\n </div>\n\n @if (data().yearKm) {\n <div class=\"c2g-tkm__year-row\">\n <span class=\"c2g-tkm__year-label\">Dieses Jahr</span>\n <span class=\"c2g-tkm__year-value\">{{ displayYearKm() | number:'1.0-0' }} km</span>\n </div>\n }\n\n @if (sparklinePath()) {\n <div class=\"c2g-tkm__chart\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 200 48\" preserveAspectRatio=\"none\" class=\"c2g-tkm__svg\">\n <defs>\n <linearGradient id=\"tkm-area-grad\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stop-color=\"var(--c2g-theme-primary, #ff6b35)\" stop-opacity=\"0.35\"/>\n <stop offset=\"100%\" stop-color=\"var(--c2g-theme-primary, #ff6b35)\" stop-opacity=\"0\"/>\n </linearGradient>\n </defs>\n <path class=\"c2g-tkm__area\" [attr.d]=\"sparklinePath()!.area\" fill=\"url(#tkm-area-grad)\" />\n <path class=\"c2g-tkm__line\" [attr.d]=\"sparklinePath()!.line\" />\n <circle\n class=\"c2g-tkm__dot\"\n [attr.cx]=\"sparklinePath()!.lastX\"\n [attr.cy]=\"sparklinePath()!.lastY\"\n r=\"3\"\n />\n </svg>\n </div>\n }\n</article>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-tkm{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));border-radius:var(--c2g-radius-xl, 20px);padding:18px 20px 0;display:flex;flex-direction:column;gap:10px;position:relative;overflow:hidden;transition:box-shadow .2s ease,transform .2s ease}.c2g-tkm:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .11));transform:translateY(-2px)}.c2g-tkm:before{content:\"\";position:absolute;inset:0;background:radial-gradient(ellipse 80% 60% at 100% 0%,rgba(255,107,53,.05) 0%,transparent 70%);pointer-events:none}.c2g-tkm__header{display:flex;align-items:center;gap:8px}.c2g-tkm__icon{font-size:1.25rem;line-height:1}.c2g-tkm__titles{display:flex;flex-direction:column;gap:2px}.c2g-tkm__title{margin:0;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-tkm__sub{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));opacity:.7}.c2g-tkm__hero{display:flex;align-items:baseline;gap:5px}.c2g-tkm__value{font-size:3rem;font-weight:800;line-height:1;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));font-variant-numeric:tabular-nums;letter-spacing:-.02em}.c2g-tkm__unit{font-size:1.25rem;font-weight:700;color:var(--c2g-theme-primary, var(--c2g-color-primary))}.c2g-tkm__year-row{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:var(--c2g-theme-surface-container, var(--c2g-color-bg-secondary));border-radius:var(--c2g-radius-md, 10px)}.c2g-tkm__year-label{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-tkm__year-value{font-size:.875rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));font-variant-numeric:tabular-nums}.c2g-tkm__chart{height:52px;margin:0 -20px}.c2g-tkm__svg{width:100%;height:100%;display:block}.c2g-tkm__line{fill:none;stroke:var(--c2g-theme-primary, var(--c2g-color-primary));stroke-width:2;stroke-linecap:round;stroke-linejoin:round}.c2g-tkm__dot{fill:var(--c2g-theme-primary, var(--c2g-color-primary))}\n"] }]
|
|
2330
|
+
}], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }] } });
|
|
2331
|
+
|
|
2332
|
+
const TYPE_CONFIG = {
|
|
2333
|
+
hiking: { label: 'Wandern', emoji: '🥾', color: '#ff6b35' },
|
|
2334
|
+
cycling: { label: 'Radfahren', emoji: '🚵', color: '#22c55e' },
|
|
2335
|
+
paddling: { label: 'Paddeln', emoji: '🛶', color: '#3b82f6' },
|
|
2336
|
+
climbing: { label: 'Klettern', emoji: '🧗', color: '#a855f7' },
|
|
2337
|
+
skiing: { label: 'Skifahren', emoji: '⛷️', color: '#06b6d4' },
|
|
2338
|
+
other: { label: 'Sonstige', emoji: '🏕️', color: '#94a3b8' },
|
|
2339
|
+
};
|
|
2340
|
+
const CIRCUMFERENCE = 2 * Math.PI * 52; // r=52
|
|
2341
|
+
class TourTypeSplitWidgetComponent {
|
|
2342
|
+
data = input.required(...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
2343
|
+
segments = computed(() => {
|
|
2344
|
+
const entries = this.data().entries.filter(e => e.count > 0);
|
|
2345
|
+
const total = entries.reduce((s, e) => s + e.count, 0) || 1;
|
|
2346
|
+
const GAP = CIRCUMFERENCE * 0.012;
|
|
2347
|
+
let offset = 0;
|
|
2348
|
+
return entries.map(e => {
|
|
2349
|
+
const pct = e.count / total;
|
|
2350
|
+
const dash = Math.max(0, pct * CIRCUMFERENCE - GAP);
|
|
2351
|
+
const cfg = TYPE_CONFIG[e.type];
|
|
2352
|
+
const seg = {
|
|
2353
|
+
type: e.type,
|
|
2354
|
+
label: cfg.label,
|
|
2355
|
+
emoji: cfg.emoji,
|
|
2356
|
+
color: cfg.color,
|
|
2357
|
+
count: e.count,
|
|
2358
|
+
km: e.km ?? 0,
|
|
2359
|
+
pct,
|
|
2360
|
+
offset,
|
|
2361
|
+
dash,
|
|
2362
|
+
};
|
|
2363
|
+
offset += pct * CIRCUMFERENCE;
|
|
2364
|
+
return seg;
|
|
2365
|
+
});
|
|
2366
|
+
}, ...(ngDevMode ? [{ debugName: "segments" }] : []));
|
|
2367
|
+
dominantSegment = computed(() => {
|
|
2368
|
+
const segs = this.segments();
|
|
2369
|
+
return segs.length > 0 ? segs.reduce((a, b) => (a.count > b.count ? a : b)) : null;
|
|
2370
|
+
}, ...(ngDevMode ? [{ debugName: "dominantSegment" }] : []));
|
|
2371
|
+
circumference = CIRCUMFERENCE;
|
|
2372
|
+
trackByType(_, seg) {
|
|
2373
|
+
return seg.type;
|
|
2374
|
+
}
|
|
2375
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: TourTypeSplitWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2376
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: TourTypeSplitWidgetComponent, isStandalone: true, selector: "c2g-tour-type-split-widget", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<article class=\"c2g-tts\">\n <header class=\"c2g-tts__header\">\n <span class=\"c2g-tts__icon\" aria-hidden=\"true\">\uD83E\uDDED</span>\n <div class=\"c2g-tts__titles\">\n <h3 class=\"c2g-tts__title\">Tour-Typen</h3>\n <span class=\"c2g-tts__sub\">{{ data().totalTours }} Touren gesamt</span>\n </div>\n </header>\n\n <div class=\"c2g-tts__body\">\n <div class=\"c2g-tts__donut-wrap\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 128 128\" class=\"c2g-tts__donut\">\n <circle cx=\"64\" cy=\"64\" r=\"52\" class=\"c2g-tts__track\"/>\n @for (seg of segments(); track trackByType($index, seg)) {\n <circle\n cx=\"64\" cy=\"64\" r=\"52\"\n class=\"c2g-tts__arc\"\n [style.stroke]=\"seg.color\"\n [style.stroke-dasharray]=\"seg.dash + ' ' + circumference\"\n [style.stroke-dashoffset]=\"-seg.offset\"\n />\n }\n </svg>\n @if (dominantSegment(); as dom) {\n <div class=\"c2g-tts__center\">\n <span class=\"c2g-tts__center-emoji\">{{ dom.emoji }}</span>\n <span class=\"c2g-tts__center-label\">{{ dom.label }}</span>\n </div>\n }\n </div>\n\n <ul class=\"c2g-tts__legend\" role=\"list\">\n @for (seg of segments(); track trackByType($index, seg)) {\n <li class=\"c2g-tts__legend-item\">\n <span class=\"c2g-tts__legend-dot\" [style.background]=\"seg.color\"></span>\n <span class=\"c2g-tts__legend-emoji\">{{ seg.emoji }}</span>\n <span class=\"c2g-tts__legend-name\">{{ seg.label }}</span>\n <span class=\"c2g-tts__legend-count\">{{ seg.count }}</span>\n <span class=\"c2g-tts__legend-pct\">{{ (seg.pct * 100) | number:'1.0-0' }}%</span>\n </li>\n }\n </ul>\n </div>\n</article>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-tts{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));border-radius:var(--c2g-radius-xl, 20px);padding:18px 20px;display:flex;flex-direction:column;gap:14px;transition:box-shadow .2s ease,transform .2s ease}.c2g-tts:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .11));transform:translateY(-2px)}.c2g-tts__header{display:flex;align-items:center;gap:8px}.c2g-tts__icon{font-size:1.25rem;line-height:1}.c2g-tts__titles{display:flex;flex-direction:column;gap:2px}.c2g-tts__title{margin:0;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-tts__sub{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));opacity:.7}.c2g-tts__body{display:flex;gap:16px;align-items:center}.c2g-tts__donut-wrap{position:relative;flex-shrink:0;width:120px;height:120px}.c2g-tts__donut{width:100%;height:100%;transform:rotate(-90deg)}.c2g-tts__track{fill:none;stroke:var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));stroke-width:18}.c2g-tts__arc{fill:none;stroke-width:18;stroke-linecap:butt;transition:stroke-dasharray .6s cubic-bezier(.4,0,.2,1)}.c2g-tts__center{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px}.c2g-tts__center-emoji{font-size:1.5rem;line-height:1}.c2g-tts__center-label{font-size:.6rem;font-weight:700;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));text-align:center;text-transform:uppercase;letter-spacing:.05em}.c2g-tts__legend{list-style:none;margin:0;padding:0;flex:1;display:flex;flex-direction:column;gap:6px}.c2g-tts__legend-item{display:flex;align-items:center;gap:6px}.c2g-tts__legend-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.c2g-tts__legend-emoji{font-size:.875rem;line-height:1;flex-shrink:0}.c2g-tts__legend-name{font-size:.8125rem;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));flex:1}.c2g-tts__legend-count{font-size:.8125rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));font-variant-numeric:tabular-nums}.c2g-tts__legend-pct{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));font-variant-numeric:tabular-nums;min-width:2.5rem;text-align:right}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1$2.DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2377
|
+
}
|
|
2378
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: TourTypeSplitWidgetComponent, decorators: [{
|
|
2379
|
+
type: Component,
|
|
2380
|
+
args: [{ selector: 'c2g-tour-type-split-widget', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<article class=\"c2g-tts\">\n <header class=\"c2g-tts__header\">\n <span class=\"c2g-tts__icon\" aria-hidden=\"true\">\uD83E\uDDED</span>\n <div class=\"c2g-tts__titles\">\n <h3 class=\"c2g-tts__title\">Tour-Typen</h3>\n <span class=\"c2g-tts__sub\">{{ data().totalTours }} Touren gesamt</span>\n </div>\n </header>\n\n <div class=\"c2g-tts__body\">\n <div class=\"c2g-tts__donut-wrap\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 128 128\" class=\"c2g-tts__donut\">\n <circle cx=\"64\" cy=\"64\" r=\"52\" class=\"c2g-tts__track\"/>\n @for (seg of segments(); track trackByType($index, seg)) {\n <circle\n cx=\"64\" cy=\"64\" r=\"52\"\n class=\"c2g-tts__arc\"\n [style.stroke]=\"seg.color\"\n [style.stroke-dasharray]=\"seg.dash + ' ' + circumference\"\n [style.stroke-dashoffset]=\"-seg.offset\"\n />\n }\n </svg>\n @if (dominantSegment(); as dom) {\n <div class=\"c2g-tts__center\">\n <span class=\"c2g-tts__center-emoji\">{{ dom.emoji }}</span>\n <span class=\"c2g-tts__center-label\">{{ dom.label }}</span>\n </div>\n }\n </div>\n\n <ul class=\"c2g-tts__legend\" role=\"list\">\n @for (seg of segments(); track trackByType($index, seg)) {\n <li class=\"c2g-tts__legend-item\">\n <span class=\"c2g-tts__legend-dot\" [style.background]=\"seg.color\"></span>\n <span class=\"c2g-tts__legend-emoji\">{{ seg.emoji }}</span>\n <span class=\"c2g-tts__legend-name\">{{ seg.label }}</span>\n <span class=\"c2g-tts__legend-count\">{{ seg.count }}</span>\n <span class=\"c2g-tts__legend-pct\">{{ (seg.pct * 100) | number:'1.0-0' }}%</span>\n </li>\n }\n </ul>\n </div>\n</article>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-tts{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));border-radius:var(--c2g-radius-xl, 20px);padding:18px 20px;display:flex;flex-direction:column;gap:14px;transition:box-shadow .2s ease,transform .2s ease}.c2g-tts:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .11));transform:translateY(-2px)}.c2g-tts__header{display:flex;align-items:center;gap:8px}.c2g-tts__icon{font-size:1.25rem;line-height:1}.c2g-tts__titles{display:flex;flex-direction:column;gap:2px}.c2g-tts__title{margin:0;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-tts__sub{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));opacity:.7}.c2g-tts__body{display:flex;gap:16px;align-items:center}.c2g-tts__donut-wrap{position:relative;flex-shrink:0;width:120px;height:120px}.c2g-tts__donut{width:100%;height:100%;transform:rotate(-90deg)}.c2g-tts__track{fill:none;stroke:var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));stroke-width:18}.c2g-tts__arc{fill:none;stroke-width:18;stroke-linecap:butt;transition:stroke-dasharray .6s cubic-bezier(.4,0,.2,1)}.c2g-tts__center{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px}.c2g-tts__center-emoji{font-size:1.5rem;line-height:1}.c2g-tts__center-label{font-size:.6rem;font-weight:700;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));text-align:center;text-transform:uppercase;letter-spacing:.05em}.c2g-tts__legend{list-style:none;margin:0;padding:0;flex:1;display:flex;flex-direction:column;gap:6px}.c2g-tts__legend-item{display:flex;align-items:center;gap:6px}.c2g-tts__legend-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.c2g-tts__legend-emoji{font-size:.875rem;line-height:1;flex-shrink:0}.c2g-tts__legend-name{font-size:.8125rem;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));flex:1}.c2g-tts__legend-count{font-size:.8125rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));font-variant-numeric:tabular-nums}.c2g-tts__legend-pct{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));font-variant-numeric:tabular-nums;min-width:2.5rem;text-align:right}\n"] }]
|
|
2381
|
+
}], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }] } });
|
|
2382
|
+
|
|
2383
|
+
const MONTH_LABELS = ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'];
|
|
2384
|
+
class TourRhythmWidgetComponent {
|
|
2385
|
+
data = input.required(...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
2386
|
+
cells = computed(() => {
|
|
2387
|
+
const weeks = this.data().weeks;
|
|
2388
|
+
let prevMonth = -1;
|
|
2389
|
+
return weeks.map((w, i) => {
|
|
2390
|
+
const d = new Date(w.weekStart);
|
|
2391
|
+
const month = d.getMonth();
|
|
2392
|
+
const monthLabel = month !== prevMonth ? MONTH_LABELS[month] : null;
|
|
2393
|
+
prevMonth = month;
|
|
2394
|
+
return { ...w, col: i, monthLabel };
|
|
2395
|
+
});
|
|
2396
|
+
}, ...(ngDevMode ? [{ debugName: "cells" }] : []));
|
|
2397
|
+
totalCols = computed(() => this.data().weeks.length, ...(ngDevMode ? [{ debugName: "totalCols" }] : []));
|
|
2398
|
+
levelColors = ['#1e293b', '#166534', '#16a34a', '#4ade80', '#86efac'];
|
|
2399
|
+
levelColor(level) {
|
|
2400
|
+
return this.levelColors[level];
|
|
2401
|
+
}
|
|
2402
|
+
trackByCol(_, cell) {
|
|
2403
|
+
return cell.col;
|
|
2404
|
+
}
|
|
2405
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: TourRhythmWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2406
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: TourRhythmWidgetComponent, isStandalone: true, selector: "c2g-tour-rhythm-widget", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<article class=\"c2g-trh\">\n <header class=\"c2g-trh__header\">\n <span class=\"c2g-trh__icon\" aria-hidden=\"true\">\uD83D\uDCC5</span>\n <div class=\"c2g-trh__titles\">\n <h3 class=\"c2g-trh__title\">Tour-Rhythmus</h3>\n <span class=\"c2g-trh__sub\">Letzte 52 Wochen</span>\n </div>\n <div class=\"c2g-trh__stats\">\n @if (data().currentStreakWeeks) {\n <span class=\"c2g-trh__streak\">\uD83D\uDD25 {{ data().currentStreakWeeks }} Wochen-Streak</span>\n }\n </div>\n </header>\n\n <div class=\"c2g-trh__grid-wrap\" aria-label=\"Aktivit\u00E4ts-Heatmap der letzten 52 Wochen\" role=\"img\">\n <div class=\"c2g-trh__month-labels\" aria-hidden=\"true\">\n @for (cell of cells(); track cell.col) {\n @if (cell.monthLabel) {\n <span class=\"c2g-trh__month-label\" [style.grid-column]=\"cell.col + 1\">\n {{ cell.monthLabel }}\n </span>\n }\n }\n </div>\n <div class=\"c2g-trh__grid\">\n @for (cell of cells(); track trackByCol($index, cell)) {\n <div\n class=\"c2g-trh__cell c2g-trh__cell--level-{{ cell.level }}\"\n [style.background]=\"levelColor(cell.level)\"\n [title]=\"cell.tourName ? cell.tourName + (cell.km ? ' \u00B7 ' + cell.km + ' km' : '') : cell.weekStart\"\n ></div>\n }\n </div>\n </div>\n\n <div class=\"c2g-trh__legend\" aria-hidden=\"true\">\n <span class=\"c2g-trh__legend-label\">Weniger</span>\n @for (l of [0, 1, 2, 3, 4]; track l) {\n <div class=\"c2g-trh__legend-cell\" [style.background]=\"levelColors[l]\"></div>\n }\n <span class=\"c2g-trh__legend-label\">Mehr</span>\n </div>\n</article>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-trh{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));border-radius:var(--c2g-radius-xl, 20px);padding:18px 20px 14px;display:flex;flex-direction:column;gap:12px;transition:box-shadow .2s ease,transform .2s ease}.c2g-trh:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .11));transform:translateY(-2px)}.c2g-trh__header{display:flex;align-items:center;gap:8px}.c2g-trh__icon{font-size:1.25rem;line-height:1}.c2g-trh__titles{flex:1;display:flex;flex-direction:column;gap:2px}.c2g-trh__title{margin:0;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-trh__sub{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));opacity:.7}.c2g-trh__streak{font-size:.72rem;font-weight:700;color:var(--c2g-color-warning, #f59e0b);background:#f59e0b1a;border:1px solid rgba(245,158,11,.2);padding:3px 8px;border-radius:999px;white-space:nowrap}.c2g-trh__grid-wrap{overflow-x:auto;overflow-y:hidden;scrollbar-width:none}.c2g-trh__grid-wrap::-webkit-scrollbar{display:none}.c2g-trh__month-labels{display:grid;grid-template-columns:repeat(52,12px);gap:2px;margin-bottom:4px;min-width:max-content}.c2g-trh__month-label{font-size:.6rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));white-space:nowrap;opacity:.6}.c2g-trh__grid{display:grid;grid-template-columns:repeat(52,12px);gap:2px;min-width:max-content}.c2g-trh__cell{width:12px;height:12px;border-radius:2px;transition:transform .15s ease,filter .15s ease;cursor:default}.c2g-trh__cell:hover{transform:scale(1.3);filter:brightness(1.2);z-index:1;position:relative}.c2g-trh__legend{display:flex;align-items:center;gap:4px}.c2g-trh__legend-label{font-size:.65rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));opacity:.6}.c2g-trh__legend-cell{width:10px;height:10px;border-radius:2px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2407
|
+
}
|
|
2408
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: TourRhythmWidgetComponent, decorators: [{
|
|
2409
|
+
type: Component,
|
|
2410
|
+
args: [{ selector: 'c2g-tour-rhythm-widget', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<article class=\"c2g-trh\">\n <header class=\"c2g-trh__header\">\n <span class=\"c2g-trh__icon\" aria-hidden=\"true\">\uD83D\uDCC5</span>\n <div class=\"c2g-trh__titles\">\n <h3 class=\"c2g-trh__title\">Tour-Rhythmus</h3>\n <span class=\"c2g-trh__sub\">Letzte 52 Wochen</span>\n </div>\n <div class=\"c2g-trh__stats\">\n @if (data().currentStreakWeeks) {\n <span class=\"c2g-trh__streak\">\uD83D\uDD25 {{ data().currentStreakWeeks }} Wochen-Streak</span>\n }\n </div>\n </header>\n\n <div class=\"c2g-trh__grid-wrap\" aria-label=\"Aktivit\u00E4ts-Heatmap der letzten 52 Wochen\" role=\"img\">\n <div class=\"c2g-trh__month-labels\" aria-hidden=\"true\">\n @for (cell of cells(); track cell.col) {\n @if (cell.monthLabel) {\n <span class=\"c2g-trh__month-label\" [style.grid-column]=\"cell.col + 1\">\n {{ cell.monthLabel }}\n </span>\n }\n }\n </div>\n <div class=\"c2g-trh__grid\">\n @for (cell of cells(); track trackByCol($index, cell)) {\n <div\n class=\"c2g-trh__cell c2g-trh__cell--level-{{ cell.level }}\"\n [style.background]=\"levelColor(cell.level)\"\n [title]=\"cell.tourName ? cell.tourName + (cell.km ? ' \u00B7 ' + cell.km + ' km' : '') : cell.weekStart\"\n ></div>\n }\n </div>\n </div>\n\n <div class=\"c2g-trh__legend\" aria-hidden=\"true\">\n <span class=\"c2g-trh__legend-label\">Weniger</span>\n @for (l of [0, 1, 2, 3, 4]; track l) {\n <div class=\"c2g-trh__legend-cell\" [style.background]=\"levelColors[l]\"></div>\n }\n <span class=\"c2g-trh__legend-label\">Mehr</span>\n </div>\n</article>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-trh{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));border-radius:var(--c2g-radius-xl, 20px);padding:18px 20px 14px;display:flex;flex-direction:column;gap:12px;transition:box-shadow .2s ease,transform .2s ease}.c2g-trh:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .11));transform:translateY(-2px)}.c2g-trh__header{display:flex;align-items:center;gap:8px}.c2g-trh__icon{font-size:1.25rem;line-height:1}.c2g-trh__titles{flex:1;display:flex;flex-direction:column;gap:2px}.c2g-trh__title{margin:0;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-trh__sub{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));opacity:.7}.c2g-trh__streak{font-size:.72rem;font-weight:700;color:var(--c2g-color-warning, #f59e0b);background:#f59e0b1a;border:1px solid rgba(245,158,11,.2);padding:3px 8px;border-radius:999px;white-space:nowrap}.c2g-trh__grid-wrap{overflow-x:auto;overflow-y:hidden;scrollbar-width:none}.c2g-trh__grid-wrap::-webkit-scrollbar{display:none}.c2g-trh__month-labels{display:grid;grid-template-columns:repeat(52,12px);gap:2px;margin-bottom:4px;min-width:max-content}.c2g-trh__month-label{font-size:.6rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));white-space:nowrap;opacity:.6}.c2g-trh__grid{display:grid;grid-template-columns:repeat(52,12px);gap:2px;min-width:max-content}.c2g-trh__cell{width:12px;height:12px;border-radius:2px;transition:transform .15s ease,filter .15s ease;cursor:default}.c2g-trh__cell:hover{transform:scale(1.3);filter:brightness(1.2);z-index:1;position:relative}.c2g-trh__legend{display:flex;align-items:center;gap:4px}.c2g-trh__legend-label{font-size:.65rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));opacity:.6}.c2g-trh__legend-cell{width:10px;height:10px;border-radius:2px}\n"] }]
|
|
2411
|
+
}], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }] } });
|
|
2412
|
+
|
|
2413
|
+
const SEASON_CONFIG = {
|
|
2414
|
+
spring: { label: 'Frühling', emoji: '🌸', color: '#84cc16' },
|
|
2415
|
+
summer: { label: 'Sommer', emoji: '☀️', color: '#f59e0b' },
|
|
2416
|
+
autumn: { label: 'Herbst', emoji: '🍂', color: '#ef4444' },
|
|
2417
|
+
winter: { label: 'Winter', emoji: '❄️', color: '#3b82f6' },
|
|
2418
|
+
};
|
|
2419
|
+
const CX = 64, CY = 64, R_OUTER = 56, R_INNER = 28;
|
|
2420
|
+
function polarToXY(cx, cy, r, angleDeg) {
|
|
2421
|
+
const rad = ((angleDeg - 90) * Math.PI) / 180;
|
|
2422
|
+
return [cx + r * Math.cos(rad), cy + r * Math.sin(rad)];
|
|
2423
|
+
}
|
|
2424
|
+
function donutSegmentPath(cx, cy, ro, ri, start, end) {
|
|
2425
|
+
const [ox1, oy1] = polarToXY(cx, cy, ro, start);
|
|
2426
|
+
const [ox2, oy2] = polarToXY(cx, cy, ro, end);
|
|
2427
|
+
const [ix1, iy1] = polarToXY(cx, cy, ri, end);
|
|
2428
|
+
const [ix2, iy2] = polarToXY(cx, cy, ri, start);
|
|
2429
|
+
const large = end - start > 180 ? 1 : 0;
|
|
2430
|
+
return [
|
|
2431
|
+
`M ${ox1} ${oy1}`,
|
|
2432
|
+
`A ${ro} ${ro} 0 ${large} 1 ${ox2} ${oy2}`,
|
|
2433
|
+
`L ${ix1} ${iy1}`,
|
|
2434
|
+
`A ${ri} ${ri} 0 ${large} 0 ${ix2} ${iy2}`,
|
|
2435
|
+
'Z',
|
|
2436
|
+
].join(' ');
|
|
2437
|
+
}
|
|
2438
|
+
const SEASON_ORDER = ['spring', 'summer', 'autumn', 'winter'];
|
|
2439
|
+
class SeasonDnaWidgetComponent {
|
|
2440
|
+
data = input.required(...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
2441
|
+
segments = computed(() => {
|
|
2442
|
+
const entries = this.data().entries;
|
|
2443
|
+
const total = entries.reduce((s, e) => s + e.tourCount, 0) || 1;
|
|
2444
|
+
const GAP = 3;
|
|
2445
|
+
let angle = 0;
|
|
2446
|
+
return SEASON_ORDER.map(season => {
|
|
2447
|
+
const entry = entries.find(e => e.season === season);
|
|
2448
|
+
const count = entry?.tourCount ?? 0;
|
|
2449
|
+
const pct = count / total;
|
|
2450
|
+
const span = Math.max(0, pct * 360 - GAP);
|
|
2451
|
+
const start = angle;
|
|
2452
|
+
const end = angle + span;
|
|
2453
|
+
angle += pct * 360;
|
|
2454
|
+
const cfg = SEASON_CONFIG[season];
|
|
2455
|
+
const mid = (start + end) / 2;
|
|
2456
|
+
const [lx, ly] = polarToXY(CX, CY, (R_OUTER + R_INNER) / 2, mid);
|
|
2457
|
+
return {
|
|
2458
|
+
season,
|
|
2459
|
+
label: cfg.label,
|
|
2460
|
+
emoji: cfg.emoji,
|
|
2461
|
+
color: cfg.color,
|
|
2462
|
+
tourCount: count,
|
|
2463
|
+
km: entry?.km ?? 0,
|
|
2464
|
+
nights: entry?.nights ?? 0,
|
|
2465
|
+
pct,
|
|
2466
|
+
startAngle: start,
|
|
2467
|
+
endAngle: end,
|
|
2468
|
+
pathD: span > 2 ? donutSegmentPath(CX, CY, R_OUTER, R_INNER, start, end) : '',
|
|
2469
|
+
labelX: lx,
|
|
2470
|
+
labelY: ly,
|
|
2471
|
+
};
|
|
2472
|
+
}).filter(s => s.pct > 0);
|
|
2473
|
+
}, ...(ngDevMode ? [{ debugName: "segments" }] : []));
|
|
2474
|
+
dominantSeason = computed(() => {
|
|
2475
|
+
const segs = this.segments();
|
|
2476
|
+
if (segs.length === 0)
|
|
2477
|
+
return null;
|
|
2478
|
+
const dom = segs.reduce((a, b) => (a.tourCount > b.tourCount ? a : b));
|
|
2479
|
+
return SEASON_CONFIG[dom.season];
|
|
2480
|
+
}, ...(ngDevMode ? [{ debugName: "dominantSeason" }] : []));
|
|
2481
|
+
trackBySeason(_, seg) {
|
|
2482
|
+
return seg.season;
|
|
2483
|
+
}
|
|
2484
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SeasonDnaWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2485
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: SeasonDnaWidgetComponent, isStandalone: true, selector: "c2g-season-dna-widget", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<article class=\"c2g-sdn\">\n <header class=\"c2g-sdn__header\">\n <span class=\"c2g-sdn__icon\" aria-hidden=\"true\">\uD83C\uDF0D</span>\n <div class=\"c2g-sdn__titles\">\n <h3 class=\"c2g-sdn__title\">Saison-DNA</h3>\n <span class=\"c2g-sdn__sub\">{{ data().totalTours }} Touren analysiert</span>\n </div>\n </header>\n\n <div class=\"c2g-sdn__body\">\n <div class=\"c2g-sdn__chart-wrap\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 128 128\" class=\"c2g-sdn__svg\">\n @for (seg of segments(); track trackBySeason($index, seg)) {\n @if (seg.pathD) {\n <path\n class=\"c2g-sdn__segment\"\n [attr.d]=\"seg.pathD\"\n [style.fill]=\"seg.color\"\n />\n }\n }\n </svg>\n @if (dominantSeason(); as dom) {\n <div class=\"c2g-sdn__center\">\n <span class=\"c2g-sdn__center-emoji\">{{ dom.emoji }}</span>\n <span class=\"c2g-sdn__center-label\">{{ dom.label }}</span>\n </div>\n }\n </div>\n\n <ul class=\"c2g-sdn__legend\" role=\"list\">\n @for (seg of segments(); track trackBySeason($index, seg)) {\n <li class=\"c2g-sdn__legend-item\">\n <span class=\"c2g-sdn__legend-dot\" [style.background]=\"seg.color\"></span>\n <span class=\"c2g-sdn__legend-emoji\">{{ seg.emoji }}</span>\n <div class=\"c2g-sdn__legend-text\">\n <span class=\"c2g-sdn__legend-name\">{{ seg.label }}</span>\n <span class=\"c2g-sdn__legend-detail\">\n {{ seg.tourCount }} Tour{{ seg.tourCount !== 1 ? 'en' : '' }}\n @if (seg.km) { \u00B7 {{ seg.km | number:'1.0-0' }} km }\n </span>\n </div>\n <span class=\"c2g-sdn__legend-pct\">{{ (seg.pct * 100) | number:'1.0-0' }}%</span>\n </li>\n }\n </ul>\n </div>\n</article>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-sdn{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));border-radius:var(--c2g-radius-xl, 20px);padding:18px 20px;display:flex;flex-direction:column;gap:14px;transition:box-shadow .2s ease,transform .2s ease}.c2g-sdn:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .11));transform:translateY(-2px)}.c2g-sdn__header{display:flex;align-items:center;gap:8px}.c2g-sdn__icon{font-size:1.25rem;line-height:1}.c2g-sdn__titles{display:flex;flex-direction:column;gap:2px}.c2g-sdn__title{margin:0;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-sdn__sub{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));opacity:.7}.c2g-sdn__body{display:flex;gap:16px;align-items:center}.c2g-sdn__chart-wrap{position:relative;flex-shrink:0;width:120px;height:120px}.c2g-sdn__svg{width:100%;height:100%;display:block}.c2g-sdn__segment{transition:opacity .2s ease;opacity:.9}.c2g-sdn__segment:hover{opacity:1;filter:brightness(1.1)}.c2g-sdn__center{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;pointer-events:none}.c2g-sdn__center-emoji{font-size:1.5rem;line-height:1}.c2g-sdn__center-label{font-size:.6rem;font-weight:700;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));text-align:center;text-transform:uppercase;letter-spacing:.05em}.c2g-sdn__legend{list-style:none;margin:0;padding:0;flex:1;display:flex;flex-direction:column;gap:8px}.c2g-sdn__legend-item{display:flex;align-items:center;gap:6px}.c2g-sdn__legend-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.c2g-sdn__legend-emoji{font-size:.875rem;line-height:1;flex-shrink:0}.c2g-sdn__legend-text{flex:1;display:flex;flex-direction:column;gap:1px}.c2g-sdn__legend-name{font-size:.8125rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}.c2g-sdn__legend-detail{font-size:.6875rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));opacity:.7}.c2g-sdn__legend-pct{font-size:.75rem;font-weight:700;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));font-variant-numeric:tabular-nums;min-width:2.25rem;text-align:right}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1$2.DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2486
|
+
}
|
|
2487
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: SeasonDnaWidgetComponent, decorators: [{
|
|
2488
|
+
type: Component,
|
|
2489
|
+
args: [{ selector: 'c2g-season-dna-widget', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<article class=\"c2g-sdn\">\n <header class=\"c2g-sdn__header\">\n <span class=\"c2g-sdn__icon\" aria-hidden=\"true\">\uD83C\uDF0D</span>\n <div class=\"c2g-sdn__titles\">\n <h3 class=\"c2g-sdn__title\">Saison-DNA</h3>\n <span class=\"c2g-sdn__sub\">{{ data().totalTours }} Touren analysiert</span>\n </div>\n </header>\n\n <div class=\"c2g-sdn__body\">\n <div class=\"c2g-sdn__chart-wrap\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 128 128\" class=\"c2g-sdn__svg\">\n @for (seg of segments(); track trackBySeason($index, seg)) {\n @if (seg.pathD) {\n <path\n class=\"c2g-sdn__segment\"\n [attr.d]=\"seg.pathD\"\n [style.fill]=\"seg.color\"\n />\n }\n }\n </svg>\n @if (dominantSeason(); as dom) {\n <div class=\"c2g-sdn__center\">\n <span class=\"c2g-sdn__center-emoji\">{{ dom.emoji }}</span>\n <span class=\"c2g-sdn__center-label\">{{ dom.label }}</span>\n </div>\n }\n </div>\n\n <ul class=\"c2g-sdn__legend\" role=\"list\">\n @for (seg of segments(); track trackBySeason($index, seg)) {\n <li class=\"c2g-sdn__legend-item\">\n <span class=\"c2g-sdn__legend-dot\" [style.background]=\"seg.color\"></span>\n <span class=\"c2g-sdn__legend-emoji\">{{ seg.emoji }}</span>\n <div class=\"c2g-sdn__legend-text\">\n <span class=\"c2g-sdn__legend-name\">{{ seg.label }}</span>\n <span class=\"c2g-sdn__legend-detail\">\n {{ seg.tourCount }} Tour{{ seg.tourCount !== 1 ? 'en' : '' }}\n @if (seg.km) { \u00B7 {{ seg.km | number:'1.0-0' }} km }\n </span>\n </div>\n <span class=\"c2g-sdn__legend-pct\">{{ (seg.pct * 100) | number:'1.0-0' }}%</span>\n </li>\n }\n </ul>\n </div>\n</article>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-sdn{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));border-radius:var(--c2g-radius-xl, 20px);padding:18px 20px;display:flex;flex-direction:column;gap:14px;transition:box-shadow .2s ease,transform .2s ease}.c2g-sdn:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .11));transform:translateY(-2px)}.c2g-sdn__header{display:flex;align-items:center;gap:8px}.c2g-sdn__icon{font-size:1.25rem;line-height:1}.c2g-sdn__titles{display:flex;flex-direction:column;gap:2px}.c2g-sdn__title{margin:0;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-sdn__sub{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));opacity:.7}.c2g-sdn__body{display:flex;gap:16px;align-items:center}.c2g-sdn__chart-wrap{position:relative;flex-shrink:0;width:120px;height:120px}.c2g-sdn__svg{width:100%;height:100%;display:block}.c2g-sdn__segment{transition:opacity .2s ease;opacity:.9}.c2g-sdn__segment:hover{opacity:1;filter:brightness(1.1)}.c2g-sdn__center{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;pointer-events:none}.c2g-sdn__center-emoji{font-size:1.5rem;line-height:1}.c2g-sdn__center-label{font-size:.6rem;font-weight:700;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));text-align:center;text-transform:uppercase;letter-spacing:.05em}.c2g-sdn__legend{list-style:none;margin:0;padding:0;flex:1;display:flex;flex-direction:column;gap:8px}.c2g-sdn__legend-item{display:flex;align-items:center;gap:6px}.c2g-sdn__legend-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.c2g-sdn__legend-emoji{font-size:.875rem;line-height:1;flex-shrink:0}.c2g-sdn__legend-text{flex:1;display:flex;flex-direction:column;gap:1px}.c2g-sdn__legend-name{font-size:.8125rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}.c2g-sdn__legend-detail{font-size:.6875rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));opacity:.7}.c2g-sdn__legend-pct{font-size:.75rem;font-weight:700;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));font-variant-numeric:tabular-nums;min-width:2.25rem;text-align:right}\n"] }]
|
|
2490
|
+
}], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }] } });
|
|
2491
|
+
|
|
2492
|
+
class WeightHistoryWidgetComponent {
|
|
2493
|
+
data = input.required(...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
2494
|
+
points = computed(() => this.data().points.slice(-12), ...(ngDevMode ? [{ debugName: "points" }] : []));
|
|
2495
|
+
sparklinePath = computed(() => {
|
|
2496
|
+
const pts = this.points();
|
|
2497
|
+
if (pts.length < 2)
|
|
2498
|
+
return null;
|
|
2499
|
+
const W = 200, H = 48;
|
|
2500
|
+
const weights = pts.map(p => p.weightG);
|
|
2501
|
+
const maxW = Math.max(...weights);
|
|
2502
|
+
const minW = Math.min(...weights);
|
|
2503
|
+
const range = maxW - minW || 1;
|
|
2504
|
+
const xs = pts.map((_, i) => (i / (pts.length - 1)) * W);
|
|
2505
|
+
const ys = pts.map(p => H - ((p.weightG - minW) / range) * (H - 8) - 2);
|
|
2506
|
+
let line = `M ${xs[0]} ${ys[0]}`;
|
|
2507
|
+
for (let i = 1; i < pts.length; i++) {
|
|
2508
|
+
const cpx = (xs[i - 1] + xs[i]) / 2;
|
|
2509
|
+
line += ` C ${cpx} ${ys[i - 1]}, ${cpx} ${ys[i]}, ${xs[i]} ${ys[i]}`;
|
|
2510
|
+
}
|
|
2511
|
+
const area = `${line} L ${xs[xs.length - 1]} ${H} L ${xs[0]} ${H} Z`;
|
|
2512
|
+
const improving = pts[pts.length - 1].weightG <= pts[0].weightG;
|
|
2513
|
+
return { line, area, lastX: xs[xs.length - 1], lastY: ys[ys.length - 1], improving };
|
|
2514
|
+
}, ...(ngDevMode ? [{ debugName: "sparklinePath" }] : []));
|
|
2515
|
+
trend = computed(() => {
|
|
2516
|
+
const pts = this.points();
|
|
2517
|
+
if (pts.length < 2)
|
|
2518
|
+
return null;
|
|
2519
|
+
const first = pts[0].weightG;
|
|
2520
|
+
const last = pts[pts.length - 1].weightG;
|
|
2521
|
+
const diffG = last - first;
|
|
2522
|
+
const pct = Math.round((diffG / first) * 100);
|
|
2523
|
+
return { diffG, pct, improving: diffG < 0 };
|
|
2524
|
+
}, ...(ngDevMode ? [{ debugName: "trend" }] : []));
|
|
2525
|
+
formatKg(g) {
|
|
2526
|
+
return (g / 1000).toFixed(2);
|
|
2527
|
+
}
|
|
2528
|
+
formatDate(iso) {
|
|
2529
|
+
return new Date(iso).toLocaleDateString('de-DE', { month: 'short', year: '2-digit' });
|
|
2530
|
+
}
|
|
2531
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: WeightHistoryWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2532
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: WeightHistoryWidgetComponent, isStandalone: true, selector: "c2g-weight-history-widget", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<article class=\"c2g-wh\">\n <header class=\"c2g-wh__header\">\n <span class=\"c2g-wh__icon\">\u2696\uFE0F</span>\n <div class=\"c2g-wh__titles\">\n <h3 class=\"c2g-wh__title\">Gewichts-Historie</h3>\n @if (data().points.length) {\n <span class=\"c2g-wh__sub\">{{ data().points.length }} Touren</span>\n }\n </div>\n @if (trend(); as t) {\n <span class=\"c2g-wh__trend\" [class.c2g-wh__trend--good]=\"t.improving\" [class.c2g-wh__trend--bad]=\"!t.improving\">\n {{ t.improving ? '\u2193' : '\u2191' }} {{ t.pct | number:'1.0-0' }}%\n </span>\n }\n </header>\n\n <div class=\"c2g-wh__hero\">\n @if (data().latestG) {\n <span class=\"c2g-wh__value\">{{ formatKg(data().latestG!) }}</span>\n <span class=\"c2g-wh__unit\">kg</span>\n <span class=\"c2g-wh__label\">aktuell</span>\n }\n @if (data().bestG) {\n <div class=\"c2g-wh__best\">\n <span class=\"c2g-wh__best-icon\">\uD83C\uDFC6</span>\n <span class=\"c2g-wh__best-val\">{{ formatKg(data().bestG!) }} kg</span>\n <span class=\"c2g-wh__best-label\">Rekord</span>\n </div>\n }\n </div>\n\n @if (sparklinePath(); as sp) {\n <div class=\"c2g-wh__chart\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 200 48\" preserveAspectRatio=\"none\" class=\"c2g-wh__svg\">\n <defs>\n <linearGradient id=\"wh-grad\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" [attr.stop-color]=\"sp.improving ? '#22c55e' : '#f97316'\" stop-opacity=\"0.3\"/>\n <stop offset=\"100%\" [attr.stop-color]=\"sp.improving ? '#22c55e' : '#f97316'\" stop-opacity=\"0\"/>\n </linearGradient>\n </defs>\n <path [attr.d]=\"sp.area\" fill=\"url(#wh-grad)\"/>\n <path class=\"c2g-wh__line\" [attr.d]=\"sp.line\"\n [style.stroke]=\"sp.improving ? '#22c55e' : '#f97316'\"/>\n <circle class=\"c2g-wh__dot\" [attr.cx]=\"sp.lastX\" [attr.cy]=\"sp.lastY\" r=\"3\"\n [style.fill]=\"sp.improving ? '#22c55e' : '#f97316'\"/>\n </svg>\n </div>\n }\n\n <div class=\"c2g-wh__dates\">\n @if (points().length >= 2) {\n <span>{{ formatDate(points()[0].date) }}</span>\n <span>{{ formatDate(points()[points().length - 1].date) }}</span>\n }\n </div>\n</article>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-wh{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));border-radius:var(--c2g-radius-xl, 20px);padding:18px 20px 0;display:flex;flex-direction:column;gap:10px;overflow:hidden;transition:box-shadow .2s ease,transform .2s ease}.c2g-wh:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .11));transform:translateY(-2px)}.c2g-wh__header{display:flex;align-items:center;gap:8px}.c2g-wh__icon{font-size:1.25rem;line-height:1}.c2g-wh__titles{flex:1;display:flex;flex-direction:column;gap:2px}.c2g-wh__title{margin:0;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-wh__sub{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));opacity:.7}.c2g-wh__trend{font-size:.8rem;font-weight:800;padding:3px 8px;border-radius:999px}.c2g-wh__trend--good{color:#22c55e;background:#22c55e1a}.c2g-wh__trend--bad{color:#f97316;background:#f973161a}.c2g-wh__hero{display:flex;align-items:baseline;gap:6px}.c2g-wh__value{font-size:2.5rem;font-weight:800;line-height:1;letter-spacing:-.02em;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));font-variant-numeric:tabular-nums}.c2g-wh__unit{font-size:1.125rem;font-weight:700;color:var(--c2g-theme-primary, var(--c2g-color-primary))}.c2g-wh__label{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-wh__best{display:flex;align-items:center;gap:5px;margin-left:auto;padding:4px 10px;border-radius:var(--c2g-radius-md, 10px);background:var(--c2g-theme-surface-container, var(--c2g-color-bg-secondary))}.c2g-wh__best-icon{font-size:.875rem}.c2g-wh__best-val{font-size:.8125rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}.c2g-wh__best-label{font-size:.6875rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-wh__chart{height:52px;margin:0 -20px}.c2g-wh__svg{width:100%;height:100%;display:block}.c2g-wh__line{fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}.c2g-wh__dates{display:flex;justify-content:space-between;padding:0 0 10px;font-size:.625rem;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));opacity:.6}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1$2.DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2533
|
+
}
|
|
2534
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: WeightHistoryWidgetComponent, decorators: [{
|
|
2535
|
+
type: Component,
|
|
2536
|
+
args: [{ selector: 'c2g-weight-history-widget', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<article class=\"c2g-wh\">\n <header class=\"c2g-wh__header\">\n <span class=\"c2g-wh__icon\">\u2696\uFE0F</span>\n <div class=\"c2g-wh__titles\">\n <h3 class=\"c2g-wh__title\">Gewichts-Historie</h3>\n @if (data().points.length) {\n <span class=\"c2g-wh__sub\">{{ data().points.length }} Touren</span>\n }\n </div>\n @if (trend(); as t) {\n <span class=\"c2g-wh__trend\" [class.c2g-wh__trend--good]=\"t.improving\" [class.c2g-wh__trend--bad]=\"!t.improving\">\n {{ t.improving ? '\u2193' : '\u2191' }} {{ t.pct | number:'1.0-0' }}%\n </span>\n }\n </header>\n\n <div class=\"c2g-wh__hero\">\n @if (data().latestG) {\n <span class=\"c2g-wh__value\">{{ formatKg(data().latestG!) }}</span>\n <span class=\"c2g-wh__unit\">kg</span>\n <span class=\"c2g-wh__label\">aktuell</span>\n }\n @if (data().bestG) {\n <div class=\"c2g-wh__best\">\n <span class=\"c2g-wh__best-icon\">\uD83C\uDFC6</span>\n <span class=\"c2g-wh__best-val\">{{ formatKg(data().bestG!) }} kg</span>\n <span class=\"c2g-wh__best-label\">Rekord</span>\n </div>\n }\n </div>\n\n @if (sparklinePath(); as sp) {\n <div class=\"c2g-wh__chart\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 200 48\" preserveAspectRatio=\"none\" class=\"c2g-wh__svg\">\n <defs>\n <linearGradient id=\"wh-grad\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" [attr.stop-color]=\"sp.improving ? '#22c55e' : '#f97316'\" stop-opacity=\"0.3\"/>\n <stop offset=\"100%\" [attr.stop-color]=\"sp.improving ? '#22c55e' : '#f97316'\" stop-opacity=\"0\"/>\n </linearGradient>\n </defs>\n <path [attr.d]=\"sp.area\" fill=\"url(#wh-grad)\"/>\n <path class=\"c2g-wh__line\" [attr.d]=\"sp.line\"\n [style.stroke]=\"sp.improving ? '#22c55e' : '#f97316'\"/>\n <circle class=\"c2g-wh__dot\" [attr.cx]=\"sp.lastX\" [attr.cy]=\"sp.lastY\" r=\"3\"\n [style.fill]=\"sp.improving ? '#22c55e' : '#f97316'\"/>\n </svg>\n </div>\n }\n\n <div class=\"c2g-wh__dates\">\n @if (points().length >= 2) {\n <span>{{ formatDate(points()[0].date) }}</span>\n <span>{{ formatDate(points()[points().length - 1].date) }}</span>\n }\n </div>\n</article>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-wh{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));border-radius:var(--c2g-radius-xl, 20px);padding:18px 20px 0;display:flex;flex-direction:column;gap:10px;overflow:hidden;transition:box-shadow .2s ease,transform .2s ease}.c2g-wh:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .11));transform:translateY(-2px)}.c2g-wh__header{display:flex;align-items:center;gap:8px}.c2g-wh__icon{font-size:1.25rem;line-height:1}.c2g-wh__titles{flex:1;display:flex;flex-direction:column;gap:2px}.c2g-wh__title{margin:0;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-wh__sub{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));opacity:.7}.c2g-wh__trend{font-size:.8rem;font-weight:800;padding:3px 8px;border-radius:999px}.c2g-wh__trend--good{color:#22c55e;background:#22c55e1a}.c2g-wh__trend--bad{color:#f97316;background:#f973161a}.c2g-wh__hero{display:flex;align-items:baseline;gap:6px}.c2g-wh__value{font-size:2.5rem;font-weight:800;line-height:1;letter-spacing:-.02em;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));font-variant-numeric:tabular-nums}.c2g-wh__unit{font-size:1.125rem;font-weight:700;color:var(--c2g-theme-primary, var(--c2g-color-primary))}.c2g-wh__label{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-wh__best{display:flex;align-items:center;gap:5px;margin-left:auto;padding:4px 10px;border-radius:var(--c2g-radius-md, 10px);background:var(--c2g-theme-surface-container, var(--c2g-color-bg-secondary))}.c2g-wh__best-icon{font-size:.875rem}.c2g-wh__best-val{font-size:.8125rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}.c2g-wh__best-label{font-size:.6875rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-wh__chart{height:52px;margin:0 -20px}.c2g-wh__svg{width:100%;height:100%;display:block}.c2g-wh__line{fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}.c2g-wh__dates{display:flex;justify-content:space-between;padding:0 0 10px;font-size:.625rem;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));opacity:.6}\n"] }]
|
|
2537
|
+
}], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }] } });
|
|
2538
|
+
|
|
2539
|
+
class GearValueWidgetComponent {
|
|
2540
|
+
data = input.required(...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
2541
|
+
topCategories = computed(() => [...(this.data().categories ?? [])]
|
|
2542
|
+
.sort((a, b) => b.value - a.value)
|
|
2543
|
+
.slice(0, 5), ...(ngDevMode ? [{ debugName: "topCategories" }] : []));
|
|
2544
|
+
maxCatValue = computed(() => Math.max(...(this.data().categories ?? []).map(c => c.value), 1), ...(ngDevMode ? [{ debugName: "maxCatValue" }] : []));
|
|
2545
|
+
formattedTotal = computed(() => {
|
|
2546
|
+
const v = this.data().totalValue;
|
|
2547
|
+
if (v >= 1000)
|
|
2548
|
+
return `${(v / 1000).toFixed(1)}k`;
|
|
2549
|
+
return v.toFixed(0);
|
|
2550
|
+
}, ...(ngDevMode ? [{ debugName: "formattedTotal" }] : []));
|
|
2551
|
+
formatValue(v) {
|
|
2552
|
+
if (v >= 1000)
|
|
2553
|
+
return `${(v / 1000).toFixed(1)}k`;
|
|
2554
|
+
return v.toFixed(0);
|
|
2555
|
+
}
|
|
2556
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GearValueWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2557
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: GearValueWidgetComponent, isStandalone: true, selector: "c2g-gear-value-widget", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<article class=\"c2g-gv\">\n <header class=\"c2g-gv__header\">\n <span class=\"c2g-gv__icon\">\uD83D\uDCB0</span>\n <div class=\"c2g-gv__titles\">\n <h3 class=\"c2g-gv__title\">Gear-Wert</h3>\n <span class=\"c2g-gv__sub\">{{ data().itemCount }} Items</span>\n </div>\n </header>\n\n <div class=\"c2g-gv__hero\">\n <span class=\"c2g-gv__value\">{{ formattedTotal() }}</span>\n <span class=\"c2g-gv__currency\">{{ data().currency }}</span>\n </div>\n\n @if (topCategories().length) {\n <ul class=\"c2g-gv__cats\" role=\"list\">\n @for (cat of topCategories(); track cat.label) {\n <li class=\"c2g-gv__cat\">\n <div class=\"c2g-gv__cat-header\">\n <span class=\"c2g-gv__cat-name\">{{ cat.label }}</span>\n <span class=\"c2g-gv__cat-value\">{{ formatValue(cat.value) }} {{ data().currency }}</span>\n </div>\n <div class=\"c2g-gv__bar-track\">\n <div class=\"c2g-gv__bar-fill\"\n [style.width.%]=\"(cat.value / maxCatValue()) * 100\">\n </div>\n </div>\n </li>\n }\n </ul>\n }\n</article>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-gv{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));border-radius:var(--c2g-radius-xl, 20px);padding:18px 20px 16px;display:flex;flex-direction:column;gap:12px;transition:box-shadow .2s ease,transform .2s ease}.c2g-gv:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .11));transform:translateY(-2px)}.c2g-gv__header{display:flex;align-items:center;gap:8px}.c2g-gv__icon{font-size:1.25rem;line-height:1}.c2g-gv__titles{display:flex;flex-direction:column;gap:2px}.c2g-gv__title{margin:0;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-gv__sub{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));opacity:.7}.c2g-gv__hero{display:flex;align-items:baseline;gap:5px}.c2g-gv__value{font-size:2.75rem;font-weight:800;line-height:1;letter-spacing:-.02em;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));font-variant-numeric:tabular-nums}.c2g-gv__currency{font-size:1.125rem;font-weight:700;color:var(--c2g-theme-primary, var(--c2g-color-primary))}.c2g-gv__cats{list-style:none;margin:0;padding:10px 0 0;display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant))}.c2g-gv__cat{display:flex;flex-direction:column;gap:4px}.c2g-gv__cat-header{display:flex;justify-content:space-between}.c2g-gv__cat-name{font-size:.8rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}.c2g-gv__cat-value{font-size:.8rem;font-weight:700;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));font-variant-numeric:tabular-nums}.c2g-gv__bar-track{height:6px;border-radius:3px;background:var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));overflow:hidden}.c2g-gv__bar-fill{height:100%;border-radius:3px;background:var(--c2g-theme-primary, var(--c2g-color-primary));transition:width .7s cubic-bezier(.4,0,.2,1)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2558
|
+
}
|
|
2559
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GearValueWidgetComponent, decorators: [{
|
|
2560
|
+
type: Component,
|
|
2561
|
+
args: [{ selector: 'c2g-gear-value-widget', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<article class=\"c2g-gv\">\n <header class=\"c2g-gv__header\">\n <span class=\"c2g-gv__icon\">\uD83D\uDCB0</span>\n <div class=\"c2g-gv__titles\">\n <h3 class=\"c2g-gv__title\">Gear-Wert</h3>\n <span class=\"c2g-gv__sub\">{{ data().itemCount }} Items</span>\n </div>\n </header>\n\n <div class=\"c2g-gv__hero\">\n <span class=\"c2g-gv__value\">{{ formattedTotal() }}</span>\n <span class=\"c2g-gv__currency\">{{ data().currency }}</span>\n </div>\n\n @if (topCategories().length) {\n <ul class=\"c2g-gv__cats\" role=\"list\">\n @for (cat of topCategories(); track cat.label) {\n <li class=\"c2g-gv__cat\">\n <div class=\"c2g-gv__cat-header\">\n <span class=\"c2g-gv__cat-name\">{{ cat.label }}</span>\n <span class=\"c2g-gv__cat-value\">{{ formatValue(cat.value) }} {{ data().currency }}</span>\n </div>\n <div class=\"c2g-gv__bar-track\">\n <div class=\"c2g-gv__bar-fill\"\n [style.width.%]=\"(cat.value / maxCatValue()) * 100\">\n </div>\n </div>\n </li>\n }\n </ul>\n }\n</article>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-gv{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));border-radius:var(--c2g-radius-xl, 20px);padding:18px 20px 16px;display:flex;flex-direction:column;gap:12px;transition:box-shadow .2s ease,transform .2s ease}.c2g-gv:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .11));transform:translateY(-2px)}.c2g-gv__header{display:flex;align-items:center;gap:8px}.c2g-gv__icon{font-size:1.25rem;line-height:1}.c2g-gv__titles{display:flex;flex-direction:column;gap:2px}.c2g-gv__title{margin:0;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-gv__sub{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));opacity:.7}.c2g-gv__hero{display:flex;align-items:baseline;gap:5px}.c2g-gv__value{font-size:2.75rem;font-weight:800;line-height:1;letter-spacing:-.02em;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));font-variant-numeric:tabular-nums}.c2g-gv__currency{font-size:1.125rem;font-weight:700;color:var(--c2g-theme-primary, var(--c2g-color-primary))}.c2g-gv__cats{list-style:none;margin:0;padding:10px 0 0;display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant))}.c2g-gv__cat{display:flex;flex-direction:column;gap:4px}.c2g-gv__cat-header{display:flex;justify-content:space-between}.c2g-gv__cat-name{font-size:.8rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}.c2g-gv__cat-value{font-size:.8rem;font-weight:700;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));font-variant-numeric:tabular-nums}.c2g-gv__bar-track{height:6px;border-radius:3px;background:var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));overflow:hidden}.c2g-gv__bar-fill{height:100%;border-radius:3px;background:var(--c2g-theme-primary, var(--c2g-color-primary));transition:width .7s cubic-bezier(.4,0,.2,1)}\n"] }]
|
|
2562
|
+
}], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }] } });
|
|
2563
|
+
|
|
2564
|
+
const DEFAULT_COLORS = [
|
|
2565
|
+
'#ff6b35', '#22c55e', '#3b82f6', '#f59e0b', '#a855f7',
|
|
2566
|
+
'#ec4899', '#06b6d4', '#84cc16', '#f97316', '#6b7280',
|
|
2567
|
+
];
|
|
2568
|
+
class WeightBreakdownWidgetComponent {
|
|
2569
|
+
data = input.required(...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
2570
|
+
entries = computed(() => {
|
|
2571
|
+
const total = this.data().totalG || 1;
|
|
2572
|
+
let offset = 0;
|
|
2573
|
+
return [...this.data().entries]
|
|
2574
|
+
.sort((a, b) => b.weightG - a.weightG)
|
|
2575
|
+
.map((e, i) => {
|
|
2576
|
+
const pct = (e.weightG / total) * 100;
|
|
2577
|
+
const entry = {
|
|
2578
|
+
...e,
|
|
2579
|
+
pct,
|
|
2580
|
+
color: e.color ?? DEFAULT_COLORS[i % DEFAULT_COLORS.length],
|
|
2581
|
+
barOffset: offset,
|
|
2582
|
+
};
|
|
2583
|
+
offset += pct;
|
|
2584
|
+
return entry;
|
|
2585
|
+
});
|
|
2586
|
+
}, ...(ngDevMode ? [{ debugName: "entries" }] : []));
|
|
2587
|
+
totalKg = computed(() => (this.data().totalG / 1000).toFixed(2), ...(ngDevMode ? [{ debugName: "totalKg" }] : []));
|
|
2588
|
+
// SVG stacked horizontal bar — single rect per entry using stroke-dasharray trick
|
|
2589
|
+
circumference = 100; // we work in percentage units
|
|
2590
|
+
formatKg(g) {
|
|
2591
|
+
if (g >= 1000)
|
|
2592
|
+
return `${(g / 1000).toFixed(2)} kg`;
|
|
2593
|
+
return `${g} g`;
|
|
2594
|
+
}
|
|
2595
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: WeightBreakdownWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2596
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: WeightBreakdownWidgetComponent, isStandalone: true, selector: "c2g-weight-breakdown-widget", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<article class=\"c2g-wb\">\n <header class=\"c2g-wb__header\">\n <span class=\"c2g-wb__icon\">\uD83C\uDFCB\uFE0F</span>\n <div class=\"c2g-wb__titles\">\n <h3 class=\"c2g-wb__title\">Gewichts-Breakdown</h3>\n <span class=\"c2g-wb__sub\">{{ totalKg() }} kg gesamt</span>\n </div>\n </header>\n\n <!-- Stacked horizontal bar -->\n <div class=\"c2g-wb__stacked\" aria-hidden=\"true\">\n @for (e of entries(); track e.category) {\n <div\n class=\"c2g-wb__stack-seg\"\n [style.width.%]=\"e.pct\"\n [style.background]=\"e.color\"\n [title]=\"e.category + ': ' + (e.pct | number:'1.0-1') + '%'\"\n ></div>\n }\n </div>\n\n <!-- Legend rows -->\n <ul class=\"c2g-wb__legend\" role=\"list\">\n @for (e of entries(); track e.category) {\n <li class=\"c2g-wb__row\">\n <span class=\"c2g-wb__dot\" [style.background]=\"e.color\"></span>\n <span class=\"c2g-wb__cat\">{{ e.category }}</span>\n <div class=\"c2g-wb__bar-track\">\n <div class=\"c2g-wb__bar-fill\" [style.width.%]=\"e.pct\" [style.background]=\"e.color\"></div>\n </div>\n <span class=\"c2g-wb__pct\">{{ e.pct | number:'1.0-1' }}%</span>\n <span class=\"c2g-wb__weight\">{{ formatKg(e.weightG) }}</span>\n </li>\n }\n </ul>\n</article>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-wb{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));border-radius:var(--c2g-radius-xl, 20px);padding:18px 20px 16px;display:flex;flex-direction:column;gap:12px;transition:box-shadow .2s ease,transform .2s ease}.c2g-wb:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .11));transform:translateY(-2px)}.c2g-wb__header{display:flex;align-items:center;gap:8px}.c2g-wb__icon{font-size:1.25rem;line-height:1}.c2g-wb__titles{display:flex;flex-direction:column;gap:2px}.c2g-wb__title{margin:0;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-wb__sub{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));opacity:.7}.c2g-wb__stacked{display:flex;height:12px;border-radius:6px;overflow:hidden;gap:1px;background:var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant))}.c2g-wb__stack-seg{height:100%;min-width:3px;transition:width .7s cubic-bezier(.4,0,.2,1)}.c2g-wb__legend{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px}.c2g-wb__row{display:grid;grid-template-columns:10px 100px 1fr 36px 72px;align-items:center;gap:8px}.c2g-wb__dot{width:10px;height:10px;border-radius:50%;flex-shrink:0}.c2g-wb__cat{font-size:.8rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c2g-wb__bar-track{height:5px;border-radius:3px;background:var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));overflow:hidden}.c2g-wb__bar-fill{height:100%;border-radius:3px;transition:width .7s cubic-bezier(.4,0,.2,1)}.c2g-wb__pct{font-size:.72rem;font-weight:700;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));font-variant-numeric:tabular-nums;text-align:right}.c2g-wb__weight{font-size:.72rem;font-weight:600;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));text-align:right;font-variant-numeric:tabular-nums}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1$2.DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2597
|
+
}
|
|
2598
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: WeightBreakdownWidgetComponent, decorators: [{
|
|
2599
|
+
type: Component,
|
|
2600
|
+
args: [{ selector: 'c2g-weight-breakdown-widget', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<article class=\"c2g-wb\">\n <header class=\"c2g-wb__header\">\n <span class=\"c2g-wb__icon\">\uD83C\uDFCB\uFE0F</span>\n <div class=\"c2g-wb__titles\">\n <h3 class=\"c2g-wb__title\">Gewichts-Breakdown</h3>\n <span class=\"c2g-wb__sub\">{{ totalKg() }} kg gesamt</span>\n </div>\n </header>\n\n <!-- Stacked horizontal bar -->\n <div class=\"c2g-wb__stacked\" aria-hidden=\"true\">\n @for (e of entries(); track e.category) {\n <div\n class=\"c2g-wb__stack-seg\"\n [style.width.%]=\"e.pct\"\n [style.background]=\"e.color\"\n [title]=\"e.category + ': ' + (e.pct | number:'1.0-1') + '%'\"\n ></div>\n }\n </div>\n\n <!-- Legend rows -->\n <ul class=\"c2g-wb__legend\" role=\"list\">\n @for (e of entries(); track e.category) {\n <li class=\"c2g-wb__row\">\n <span class=\"c2g-wb__dot\" [style.background]=\"e.color\"></span>\n <span class=\"c2g-wb__cat\">{{ e.category }}</span>\n <div class=\"c2g-wb__bar-track\">\n <div class=\"c2g-wb__bar-fill\" [style.width.%]=\"e.pct\" [style.background]=\"e.color\"></div>\n </div>\n <span class=\"c2g-wb__pct\">{{ e.pct | number:'1.0-1' }}%</span>\n <span class=\"c2g-wb__weight\">{{ formatKg(e.weightG) }}</span>\n </li>\n }\n </ul>\n</article>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-wb{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));border-radius:var(--c2g-radius-xl, 20px);padding:18px 20px 16px;display:flex;flex-direction:column;gap:12px;transition:box-shadow .2s ease,transform .2s ease}.c2g-wb:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .11));transform:translateY(-2px)}.c2g-wb__header{display:flex;align-items:center;gap:8px}.c2g-wb__icon{font-size:1.25rem;line-height:1}.c2g-wb__titles{display:flex;flex-direction:column;gap:2px}.c2g-wb__title{margin:0;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-wb__sub{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));opacity:.7}.c2g-wb__stacked{display:flex;height:12px;border-radius:6px;overflow:hidden;gap:1px;background:var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant))}.c2g-wb__stack-seg{height:100%;min-width:3px;transition:width .7s cubic-bezier(.4,0,.2,1)}.c2g-wb__legend{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px}.c2g-wb__row{display:grid;grid-template-columns:10px 100px 1fr 36px 72px;align-items:center;gap:8px}.c2g-wb__dot{width:10px;height:10px;border-radius:50%;flex-shrink:0}.c2g-wb__cat{font-size:.8rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c2g-wb__bar-track{height:5px;border-radius:3px;background:var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));overflow:hidden}.c2g-wb__bar-fill{height:100%;border-radius:3px;transition:width .7s cubic-bezier(.4,0,.2,1)}.c2g-wb__pct{font-size:.72rem;font-weight:700;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));font-variant-numeric:tabular-nums;text-align:right}.c2g-wb__weight{font-size:.72rem;font-weight:600;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));text-align:right;font-variant-numeric:tabular-nums}\n"] }]
|
|
2601
|
+
}], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }] } });
|
|
2602
|
+
|
|
2603
|
+
class AdventureScoreWidgetComponent {
|
|
2604
|
+
data = input.required(...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
2605
|
+
displayScore = signal(0, ...(ngDevMode ? [{ debugName: "displayScore" }] : []));
|
|
2606
|
+
raf = 0;
|
|
2607
|
+
constructor() {
|
|
2608
|
+
effect(() => {
|
|
2609
|
+
const target = this.data().score;
|
|
2610
|
+
cancelAnimationFrame(this.raf);
|
|
2611
|
+
const start = performance.now();
|
|
2612
|
+
const tick = (now) => {
|
|
2613
|
+
const t = Math.min((now - start) / 1200, 1);
|
|
2614
|
+
const e = 1 - Math.pow(1 - t, 4);
|
|
2615
|
+
this.displayScore.set(Math.round(e * target));
|
|
2616
|
+
if (t < 1)
|
|
2617
|
+
this.raf = requestAnimationFrame(tick);
|
|
2618
|
+
};
|
|
2619
|
+
this.raf = requestAnimationFrame(tick);
|
|
2620
|
+
});
|
|
2621
|
+
}
|
|
2622
|
+
// Circular progress arc (0–100)
|
|
2623
|
+
arcPath = computed(() => {
|
|
2624
|
+
const pct = Math.min(this.data().score / 100, 1);
|
|
2625
|
+
const r = 52, cx = 60, cy = 60;
|
|
2626
|
+
const angle = pct * 360;
|
|
2627
|
+
if (angle >= 359.9) {
|
|
2628
|
+
return `M ${cx} ${cy - r} A ${r} ${r} 0 1 1 ${cx - 0.01} ${cy - r}`;
|
|
2629
|
+
}
|
|
2630
|
+
const rad = ((angle - 90) * Math.PI) / 180;
|
|
2631
|
+
const x = cx + r * Math.cos(rad);
|
|
2632
|
+
const y = cy + r * Math.sin(rad);
|
|
2633
|
+
const large = angle > 180 ? 1 : 0;
|
|
2634
|
+
return `M ${cx} ${cy - r} A ${r} ${r} 0 ${large} 1 ${x} ${y}`;
|
|
2635
|
+
}, ...(ngDevMode ? [{ debugName: "arcPath" }] : []));
|
|
2636
|
+
trackPath = `M 60 8 A 52 52 0 1 1 59.99 8`;
|
|
2637
|
+
levelColor = computed(() => {
|
|
2638
|
+
const l = this.data().level;
|
|
2639
|
+
if (l >= 80)
|
|
2640
|
+
return '#f59e0b';
|
|
2641
|
+
if (l >= 60)
|
|
2642
|
+
return '#ff6b35';
|
|
2643
|
+
if (l >= 40)
|
|
2644
|
+
return '#22c55e';
|
|
2645
|
+
if (l >= 20)
|
|
2646
|
+
return '#3b82f6';
|
|
2647
|
+
return '#94a3b8';
|
|
2648
|
+
}, ...(ngDevMode ? [{ debugName: "levelColor" }] : []));
|
|
2649
|
+
sparklinePath = computed(() => {
|
|
2650
|
+
const pts = this.data().sparkline;
|
|
2651
|
+
if (!pts || pts.length < 2)
|
|
2652
|
+
return null;
|
|
2653
|
+
const W = 120, H = 28;
|
|
2654
|
+
const maxS = Math.max(...pts.map(p => p.score));
|
|
2655
|
+
const minS = Math.min(...pts.map(p => p.score));
|
|
2656
|
+
const range = maxS - minS || 1;
|
|
2657
|
+
const xs = pts.map((_, i) => (i / (pts.length - 1)) * W);
|
|
2658
|
+
const ys = pts.map(p => H - ((p.score - minS) / range) * (H - 4) - 2);
|
|
2659
|
+
let d = `M ${xs[0]} ${ys[0]}`;
|
|
2660
|
+
for (let i = 1; i < pts.length; i++) {
|
|
2661
|
+
const cpx = (xs[i - 1] + xs[i]) / 2;
|
|
2662
|
+
d += ` C ${cpx} ${ys[i - 1]}, ${cpx} ${ys[i]}, ${xs[i]} ${ys[i]}`;
|
|
2663
|
+
}
|
|
2664
|
+
return d;
|
|
2665
|
+
}, ...(ngDevMode ? [{ debugName: "sparklinePath" }] : []));
|
|
2666
|
+
ngOnDestroy() { cancelAnimationFrame(this.raf); }
|
|
2667
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AdventureScoreWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2668
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: AdventureScoreWidgetComponent, isStandalone: true, selector: "c2g-adventure-score-widget", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<article class=\"c2g-as\">\n <header class=\"c2g-as__header\">\n <span class=\"c2g-as__icon\">\uD83C\uDFC6</span>\n <div class=\"c2g-as__titles\">\n <h3 class=\"c2g-as__title\">Abenteuer-Score</h3>\n <span class=\"c2g-as__level-label\">{{ data().levelLabel }}</span>\n </div>\n @if (sparklinePath(); as sp) {\n <svg class=\"c2g-as__sparkline\" viewBox=\"0 0 120 28\" aria-hidden=\"true\">\n <path [attr.d]=\"sp\" [style.stroke]=\"levelColor()\" class=\"c2g-as__spark-line\"/>\n </svg>\n }\n </header>\n\n <div class=\"c2g-as__body\">\n <!-- Circular progress -->\n <div class=\"c2g-as__circle-wrap\" aria-hidden=\"true\">\n <svg class=\"c2g-as__circle\" viewBox=\"0 0 120 120\">\n <path class=\"c2g-as__track\" [attr.d]=\"trackPath\" fill=\"none\" stroke-width=\"10\" stroke-linecap=\"round\"/>\n <path class=\"c2g-as__arc\" [attr.d]=\"arcPath()\" fill=\"none\" stroke-width=\"10\" stroke-linecap=\"round\"\n [style.stroke]=\"levelColor()\"/>\n </svg>\n <div class=\"c2g-as__center\">\n <span class=\"c2g-as__score\" [style.color]=\"levelColor()\">{{ displayScore() }}</span>\n <span class=\"c2g-as__score-max\">/100</span>\n </div>\n </div>\n\n <!-- Stats -->\n <div class=\"c2g-as__stats\">\n <div class=\"c2g-as__stat\">\n <span class=\"c2g-as__stat-val\">{{ data().totalTours }}</span>\n <span class=\"c2g-as__stat-label\">Touren</span>\n </div>\n <div class=\"c2g-as__stat\">\n <span class=\"c2g-as__stat-val\">{{ data().totalKm | number:'1.0-0' }}</span>\n <span class=\"c2g-as__stat-label\">km</span>\n </div>\n <div class=\"c2g-as__stat\">\n <span class=\"c2g-as__stat-val\">{{ data().totalNights }}</span>\n <span class=\"c2g-as__stat-label\">N\u00E4chte</span>\n </div>\n <div class=\"c2g-as__stat\">\n <span class=\"c2g-as__stat-val\">{{ data().uniqueTypes }}</span>\n <span class=\"c2g-as__stat-label\">Typen</span>\n </div>\n </div>\n </div>\n</article>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-as{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));border-radius:var(--c2g-radius-xl, 20px);padding:18px 20px 16px;display:flex;flex-direction:column;gap:14px;transition:box-shadow .2s ease,transform .2s ease}.c2g-as:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .11));transform:translateY(-2px)}.c2g-as__header{display:flex;align-items:flex-start;gap:8px}.c2g-as__icon{font-size:1.375rem;line-height:1}.c2g-as__titles{flex:1;display:flex;flex-direction:column;gap:2px}.c2g-as__title{margin:0;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-as__level-label{font-size:.8rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}.c2g-as__sparkline{width:80px;height:28px;flex-shrink:0}.c2g-as__spark-line{fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;opacity:.7;stroke:var(--c2g-theme-primary, var(--c2g-color-primary))}.c2g-as__body{display:flex;gap:16px;align-items:center}.c2g-as__circle-wrap{position:relative;width:120px;height:120px;flex-shrink:0}.c2g-as__circle{width:100%;height:100%;display:block}.c2g-as__track{stroke:var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant))}.c2g-as__arc{transition:d .9s cubic-bezier(.4,0,.2,1)}.c2g-as__center{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px}.c2g-as__score{font-size:2.25rem;font-weight:900;line-height:1;letter-spacing:-.03em;font-variant-numeric:tabular-nums}.c2g-as__score-max{font-size:.75rem;font-weight:600;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-as__stats{display:grid;grid-template-columns:1fr 1fr;gap:10px;flex:1}.c2g-as__stat{display:flex;flex-direction:column;gap:2px;padding:8px 10px;background:var(--c2g-theme-surface-container, var(--c2g-color-bg-secondary));border-radius:var(--c2g-radius-md, 10px)}.c2g-as__stat-val{font-size:1.25rem;font-weight:800;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));font-variant-numeric:tabular-nums;line-height:1}.c2g-as__stat-label{font-size:.65rem;font-weight:600;text-transform:uppercase;letter-spacing:.07em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1$2.DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2669
|
+
}
|
|
2670
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AdventureScoreWidgetComponent, decorators: [{
|
|
2671
|
+
type: Component,
|
|
2672
|
+
args: [{ selector: 'c2g-adventure-score-widget', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<article class=\"c2g-as\">\n <header class=\"c2g-as__header\">\n <span class=\"c2g-as__icon\">\uD83C\uDFC6</span>\n <div class=\"c2g-as__titles\">\n <h3 class=\"c2g-as__title\">Abenteuer-Score</h3>\n <span class=\"c2g-as__level-label\">{{ data().levelLabel }}</span>\n </div>\n @if (sparklinePath(); as sp) {\n <svg class=\"c2g-as__sparkline\" viewBox=\"0 0 120 28\" aria-hidden=\"true\">\n <path [attr.d]=\"sp\" [style.stroke]=\"levelColor()\" class=\"c2g-as__spark-line\"/>\n </svg>\n }\n </header>\n\n <div class=\"c2g-as__body\">\n <!-- Circular progress -->\n <div class=\"c2g-as__circle-wrap\" aria-hidden=\"true\">\n <svg class=\"c2g-as__circle\" viewBox=\"0 0 120 120\">\n <path class=\"c2g-as__track\" [attr.d]=\"trackPath\" fill=\"none\" stroke-width=\"10\" stroke-linecap=\"round\"/>\n <path class=\"c2g-as__arc\" [attr.d]=\"arcPath()\" fill=\"none\" stroke-width=\"10\" stroke-linecap=\"round\"\n [style.stroke]=\"levelColor()\"/>\n </svg>\n <div class=\"c2g-as__center\">\n <span class=\"c2g-as__score\" [style.color]=\"levelColor()\">{{ displayScore() }}</span>\n <span class=\"c2g-as__score-max\">/100</span>\n </div>\n </div>\n\n <!-- Stats -->\n <div class=\"c2g-as__stats\">\n <div class=\"c2g-as__stat\">\n <span class=\"c2g-as__stat-val\">{{ data().totalTours }}</span>\n <span class=\"c2g-as__stat-label\">Touren</span>\n </div>\n <div class=\"c2g-as__stat\">\n <span class=\"c2g-as__stat-val\">{{ data().totalKm | number:'1.0-0' }}</span>\n <span class=\"c2g-as__stat-label\">km</span>\n </div>\n <div class=\"c2g-as__stat\">\n <span class=\"c2g-as__stat-val\">{{ data().totalNights }}</span>\n <span class=\"c2g-as__stat-label\">N\u00E4chte</span>\n </div>\n <div class=\"c2g-as__stat\">\n <span class=\"c2g-as__stat-val\">{{ data().uniqueTypes }}</span>\n <span class=\"c2g-as__stat-label\">Typen</span>\n </div>\n </div>\n </div>\n</article>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-as{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));border-radius:var(--c2g-radius-xl, 20px);padding:18px 20px 16px;display:flex;flex-direction:column;gap:14px;transition:box-shadow .2s ease,transform .2s ease}.c2g-as:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .11));transform:translateY(-2px)}.c2g-as__header{display:flex;align-items:flex-start;gap:8px}.c2g-as__icon{font-size:1.375rem;line-height:1}.c2g-as__titles{flex:1;display:flex;flex-direction:column;gap:2px}.c2g-as__title{margin:0;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-as__level-label{font-size:.8rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}.c2g-as__sparkline{width:80px;height:28px;flex-shrink:0}.c2g-as__spark-line{fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;opacity:.7;stroke:var(--c2g-theme-primary, var(--c2g-color-primary))}.c2g-as__body{display:flex;gap:16px;align-items:center}.c2g-as__circle-wrap{position:relative;width:120px;height:120px;flex-shrink:0}.c2g-as__circle{width:100%;height:100%;display:block}.c2g-as__track{stroke:var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant))}.c2g-as__arc{transition:d .9s cubic-bezier(.4,0,.2,1)}.c2g-as__center{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px}.c2g-as__score{font-size:2.25rem;font-weight:900;line-height:1;letter-spacing:-.03em;font-variant-numeric:tabular-nums}.c2g-as__score-max{font-size:.75rem;font-weight:600;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-as__stats{display:grid;grid-template-columns:1fr 1fr;gap:10px;flex:1}.c2g-as__stat{display:flex;flex-direction:column;gap:2px;padding:8px 10px;background:var(--c2g-theme-surface-container, var(--c2g-color-bg-secondary));border-radius:var(--c2g-radius-md, 10px)}.c2g-as__stat-val{font-size:1.25rem;font-weight:800;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));font-variant-numeric:tabular-nums;line-height:1}.c2g-as__stat-label{font-size:.65rem;font-weight:600;text-transform:uppercase;letter-spacing:.07em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}\n"] }]
|
|
2673
|
+
}], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }] } });
|
|
2674
|
+
|
|
2675
|
+
class TopGearWidgetComponent {
|
|
2676
|
+
data = input.required(...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
2677
|
+
top = computed(() => this.data().entries.slice(0, 5), ...(ngDevMode ? [{ debugName: "top" }] : []));
|
|
2678
|
+
maxUsage = computed(() => Math.max(...this.data().entries.map(e => e.usageCount), 1), ...(ngDevMode ? [{ debugName: "maxUsage" }] : []));
|
|
2679
|
+
usagePct(count) {
|
|
2680
|
+
return Math.round((count / this.maxUsage()) * 100);
|
|
2681
|
+
}
|
|
2682
|
+
medal(index) {
|
|
2683
|
+
return ['🥇', '🥈', '🥉', '', ''][index] ?? '';
|
|
2684
|
+
}
|
|
2685
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: TopGearWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2686
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: TopGearWidgetComponent, isStandalone: true, selector: "c2g-top-gear-widget", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<article class=\"c2g-tg\">\n <header class=\"c2g-tg__header\">\n <span class=\"c2g-tg__icon\">\uD83C\uDF92</span>\n <h3 class=\"c2g-tg__title\">Top-Gear</h3>\n </header>\n\n <ol class=\"c2g-tg__list\" role=\"list\">\n @for (entry of top(); track entry.name; let i = $index) {\n <li class=\"c2g-tg__item\" [class.c2g-tg__item--top3]=\"i < 3\">\n <span class=\"c2g-tg__rank\">\n @if (medal(i)) { {{ medal(i) }} } @else { {{ i + 1 }} }\n </span>\n <div class=\"c2g-tg__info\">\n <span class=\"c2g-tg__name\">{{ entry.name }}</span>\n @if (entry.category) {\n <span class=\"c2g-tg__cat\">{{ entry.category }}</span>\n }\n </div>\n <div class=\"c2g-tg__bar-track\">\n <div class=\"c2g-tg__bar-fill\" [style.width.%]=\"usagePct(entry.usageCount)\"></div>\n </div>\n <span class=\"c2g-tg__count\">{{ entry.usageCount }}\u00D7</span>\n </li>\n }\n </ol>\n</article>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-tg{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));border-radius:var(--c2g-radius-xl, 20px);padding:18px 20px 16px;display:flex;flex-direction:column;gap:12px;transition:box-shadow .2s ease,transform .2s ease}.c2g-tg:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .11));transform:translateY(-2px)}.c2g-tg__header{display:flex;align-items:center;gap:8px}.c2g-tg__icon{font-size:1.25rem;line-height:1}.c2g-tg__title{margin:0;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-tg__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px;counter-reset:gear}.c2g-tg__item{display:grid;grid-template-columns:28px 1fr 1fr 36px;align-items:center;gap:8px;padding:8px 10px;border-radius:var(--c2g-radius-md, 10px);background:var(--c2g-theme-surface-container-low, var(--c2g-color-bg-base))}.c2g-tg__item--top3{background:var(--c2g-theme-surface-container, var(--c2g-color-bg-secondary))}.c2g-tg__rank{font-size:1rem;text-align:center}.c2g-tg__info{display:flex;flex-direction:column;gap:1px;overflow:hidden}.c2g-tg__name{font-size:.8125rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c2g-tg__cat{font-size:.65rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-tg__bar-track{height:6px;border-radius:3px;background:var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));overflow:hidden}.c2g-tg__bar-fill{height:100%;border-radius:3px;background:var(--c2g-theme-primary, var(--c2g-color-primary));transition:width .7s cubic-bezier(.4,0,.2,1)}.c2g-tg__count{font-size:.8rem;font-weight:800;text-align:right;color:var(--c2g-theme-primary, var(--c2g-color-primary));font-variant-numeric:tabular-nums}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2687
|
+
}
|
|
2688
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: TopGearWidgetComponent, decorators: [{
|
|
2689
|
+
type: Component,
|
|
2690
|
+
args: [{ selector: 'c2g-top-gear-widget', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<article class=\"c2g-tg\">\n <header class=\"c2g-tg__header\">\n <span class=\"c2g-tg__icon\">\uD83C\uDF92</span>\n <h3 class=\"c2g-tg__title\">Top-Gear</h3>\n </header>\n\n <ol class=\"c2g-tg__list\" role=\"list\">\n @for (entry of top(); track entry.name; let i = $index) {\n <li class=\"c2g-tg__item\" [class.c2g-tg__item--top3]=\"i < 3\">\n <span class=\"c2g-tg__rank\">\n @if (medal(i)) { {{ medal(i) }} } @else { {{ i + 1 }} }\n </span>\n <div class=\"c2g-tg__info\">\n <span class=\"c2g-tg__name\">{{ entry.name }}</span>\n @if (entry.category) {\n <span class=\"c2g-tg__cat\">{{ entry.category }}</span>\n }\n </div>\n <div class=\"c2g-tg__bar-track\">\n <div class=\"c2g-tg__bar-fill\" [style.width.%]=\"usagePct(entry.usageCount)\"></div>\n </div>\n <span class=\"c2g-tg__count\">{{ entry.usageCount }}\u00D7</span>\n </li>\n }\n </ol>\n</article>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-tg{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));border-radius:var(--c2g-radius-xl, 20px);padding:18px 20px 16px;display:flex;flex-direction:column;gap:12px;transition:box-shadow .2s ease,transform .2s ease}.c2g-tg:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .11));transform:translateY(-2px)}.c2g-tg__header{display:flex;align-items:center;gap:8px}.c2g-tg__icon{font-size:1.25rem;line-height:1}.c2g-tg__title{margin:0;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-tg__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px;counter-reset:gear}.c2g-tg__item{display:grid;grid-template-columns:28px 1fr 1fr 36px;align-items:center;gap:8px;padding:8px 10px;border-radius:var(--c2g-radius-md, 10px);background:var(--c2g-theme-surface-container-low, var(--c2g-color-bg-base))}.c2g-tg__item--top3{background:var(--c2g-theme-surface-container, var(--c2g-color-bg-secondary))}.c2g-tg__rank{font-size:1rem;text-align:center}.c2g-tg__info{display:flex;flex-direction:column;gap:1px;overflow:hidden}.c2g-tg__name{font-size:.8125rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c2g-tg__cat{font-size:.65rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-tg__bar-track{height:6px;border-radius:3px;background:var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));overflow:hidden}.c2g-tg__bar-fill{height:100%;border-radius:3px;background:var(--c2g-theme-primary, var(--c2g-color-primary));transition:width .7s cubic-bezier(.4,0,.2,1)}.c2g-tg__count{font-size:.8rem;font-weight:800;text-align:right;color:var(--c2g-theme-primary, var(--c2g-color-primary));font-variant-numeric:tabular-nums}\n"] }]
|
|
2691
|
+
}], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }] } });
|
|
2692
|
+
|
|
2693
|
+
class GroupActivityWidgetComponent {
|
|
2694
|
+
data = input.required(...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
2695
|
+
ranked = computed(() => {
|
|
2696
|
+
const sorted = [...this.data().members]
|
|
2697
|
+
.sort((a, b) => b.completedItems - a.completedItems);
|
|
2698
|
+
const maxItems = Math.max(...sorted.map(m => m.completedItems), 1);
|
|
2699
|
+
return sorted.map((m, i) => ({
|
|
2700
|
+
...m,
|
|
2701
|
+
rank: i + 1,
|
|
2702
|
+
pct: Math.round((m.completedItems / maxItems) * 100),
|
|
2703
|
+
}));
|
|
2704
|
+
}, ...(ngDevMode ? [{ debugName: "ranked" }] : []));
|
|
2705
|
+
medal(rank) {
|
|
2706
|
+
if (rank === 1)
|
|
2707
|
+
return '🥇';
|
|
2708
|
+
if (rank === 2)
|
|
2709
|
+
return '🥈';
|
|
2710
|
+
if (rank === 3)
|
|
2711
|
+
return '🥉';
|
|
2712
|
+
return '';
|
|
2713
|
+
}
|
|
2714
|
+
completionPct(m) {
|
|
2715
|
+
if (!m.totalItems)
|
|
2716
|
+
return 0;
|
|
2717
|
+
return Math.round((m.completedItems / m.totalItems) * 100);
|
|
2718
|
+
}
|
|
2719
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GroupActivityWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2720
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: GroupActivityWidgetComponent, isStandalone: true, selector: "c2g-group-activity-widget", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<article class=\"c2g-ga\">\n <header class=\"c2g-ga__header\">\n <span class=\"c2g-ga__icon\">\uD83C\uDFC5</span>\n <div class=\"c2g-ga__titles\">\n <h3 class=\"c2g-ga__title\">Aktivste Mitglieder</h3>\n @if (data().groupName) {\n <span class=\"c2g-ga__sub\">{{ data().groupName }}</span>\n }\n </div>\n </header>\n\n <ol class=\"c2g-ga__list\" role=\"list\">\n @for (m of ranked(); track m.name) {\n <li class=\"c2g-ga__item\" [class.c2g-ga__item--self]=\"m.isSelf\">\n <!-- Avatar -->\n <div class=\"c2g-ga__avatar\">\n @if (m.avatarUrl) {\n <img [src]=\"m.avatarUrl\" [alt]=\"m.name\" class=\"c2g-ga__avatar-img\"/>\n } @else {\n <span class=\"c2g-ga__initials\">{{ m.initials }}</span>\n }\n @if (medal(m.rank)) {\n <span class=\"c2g-ga__medal\">{{ medal(m.rank) }}</span>\n }\n </div>\n\n <div class=\"c2g-ga__info\">\n <span class=\"c2g-ga__name\">{{ m.name }}{{ m.isSelf ? ' (du)' : '' }}</span>\n <div class=\"c2g-ga__bar-track\">\n <div class=\"c2g-ga__bar-fill\" [style.width.%]=\"m.pct\"></div>\n </div>\n </div>\n\n <div class=\"c2g-ga__nums\">\n <span class=\"c2g-ga__completed\">{{ m.completedItems }}</span>\n @if (m.totalItems) {\n <span class=\"c2g-ga__total\">/ {{ m.totalItems }}</span>\n }\n </div>\n </li>\n }\n </ol>\n</article>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-ga{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));border-radius:var(--c2g-radius-xl, 20px);padding:18px 20px 16px;display:flex;flex-direction:column;gap:12px;transition:box-shadow .2s ease,transform .2s ease}.c2g-ga:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .11));transform:translateY(-2px)}.c2g-ga__header{display:flex;align-items:center;gap:8px}.c2g-ga__icon{font-size:1.25rem;line-height:1}.c2g-ga__titles{display:flex;flex-direction:column;gap:2px}.c2g-ga__title{margin:0;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-ga__sub{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));opacity:.7}.c2g-ga__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.c2g-ga__item{display:grid;grid-template-columns:36px 1fr auto;align-items:center;gap:10px}.c2g-ga__item--self{background:var(--c2g-color-primary-light, #fff4f0);border-radius:var(--c2g-radius-md, 10px);padding:4px 8px}.c2g-ga__avatar{position:relative;width:36px;height:36px;flex-shrink:0;border-radius:50%;background:var(--c2g-theme-surface-container, var(--c2g-color-bg-secondary));display:flex;align-items:center;justify-content:center;overflow:visible}.c2g-ga__avatar-img{width:100%;height:100%;border-radius:50%;object-fit:cover}.c2g-ga__initials{font-size:.75rem;font-weight:800;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}.c2g-ga__medal{position:absolute;bottom:-4px;right:-4px;font-size:.75rem;line-height:1}.c2g-ga__info{display:flex;flex-direction:column;gap:4px;overflow:hidden}.c2g-ga__name{font-size:.8125rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c2g-ga__bar-track{height:5px;border-radius:3px;background:var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));overflow:hidden}.c2g-ga__bar-fill{height:100%;border-radius:3px;background:var(--c2g-theme-primary, var(--c2g-color-primary));transition:width .7s cubic-bezier(.4,0,.2,1)}.c2g-ga__nums{display:flex;align-items:baseline;gap:2px}.c2g-ga__completed{font-size:1rem;font-weight:900;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));font-variant-numeric:tabular-nums}.c2g-ga__total{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));font-variant-numeric:tabular-nums}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2721
|
+
}
|
|
2722
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GroupActivityWidgetComponent, decorators: [{
|
|
2723
|
+
type: Component,
|
|
2724
|
+
args: [{ selector: 'c2g-group-activity-widget', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<article class=\"c2g-ga\">\n <header class=\"c2g-ga__header\">\n <span class=\"c2g-ga__icon\">\uD83C\uDFC5</span>\n <div class=\"c2g-ga__titles\">\n <h3 class=\"c2g-ga__title\">Aktivste Mitglieder</h3>\n @if (data().groupName) {\n <span class=\"c2g-ga__sub\">{{ data().groupName }}</span>\n }\n </div>\n </header>\n\n <ol class=\"c2g-ga__list\" role=\"list\">\n @for (m of ranked(); track m.name) {\n <li class=\"c2g-ga__item\" [class.c2g-ga__item--self]=\"m.isSelf\">\n <!-- Avatar -->\n <div class=\"c2g-ga__avatar\">\n @if (m.avatarUrl) {\n <img [src]=\"m.avatarUrl\" [alt]=\"m.name\" class=\"c2g-ga__avatar-img\"/>\n } @else {\n <span class=\"c2g-ga__initials\">{{ m.initials }}</span>\n }\n @if (medal(m.rank)) {\n <span class=\"c2g-ga__medal\">{{ medal(m.rank) }}</span>\n }\n </div>\n\n <div class=\"c2g-ga__info\">\n <span class=\"c2g-ga__name\">{{ m.name }}{{ m.isSelf ? ' (du)' : '' }}</span>\n <div class=\"c2g-ga__bar-track\">\n <div class=\"c2g-ga__bar-fill\" [style.width.%]=\"m.pct\"></div>\n </div>\n </div>\n\n <div class=\"c2g-ga__nums\">\n <span class=\"c2g-ga__completed\">{{ m.completedItems }}</span>\n @if (m.totalItems) {\n <span class=\"c2g-ga__total\">/ {{ m.totalItems }}</span>\n }\n </div>\n </li>\n }\n </ol>\n</article>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-ga{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));border-radius:var(--c2g-radius-xl, 20px);padding:18px 20px 16px;display:flex;flex-direction:column;gap:12px;transition:box-shadow .2s ease,transform .2s ease}.c2g-ga:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .11));transform:translateY(-2px)}.c2g-ga__header{display:flex;align-items:center;gap:8px}.c2g-ga__icon{font-size:1.25rem;line-height:1}.c2g-ga__titles{display:flex;flex-direction:column;gap:2px}.c2g-ga__title{margin:0;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-ga__sub{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));opacity:.7}.c2g-ga__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.c2g-ga__item{display:grid;grid-template-columns:36px 1fr auto;align-items:center;gap:10px}.c2g-ga__item--self{background:var(--c2g-color-primary-light, #fff4f0);border-radius:var(--c2g-radius-md, 10px);padding:4px 8px}.c2g-ga__avatar{position:relative;width:36px;height:36px;flex-shrink:0;border-radius:50%;background:var(--c2g-theme-surface-container, var(--c2g-color-bg-secondary));display:flex;align-items:center;justify-content:center;overflow:visible}.c2g-ga__avatar-img{width:100%;height:100%;border-radius:50%;object-fit:cover}.c2g-ga__initials{font-size:.75rem;font-weight:800;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}.c2g-ga__medal{position:absolute;bottom:-4px;right:-4px;font-size:.75rem;line-height:1}.c2g-ga__info{display:flex;flex-direction:column;gap:4px;overflow:hidden}.c2g-ga__name{font-size:.8125rem;font-weight:700;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c2g-ga__bar-track{height:5px;border-radius:3px;background:var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));overflow:hidden}.c2g-ga__bar-fill{height:100%;border-radius:3px;background:var(--c2g-theme-primary, var(--c2g-color-primary));transition:width .7s cubic-bezier(.4,0,.2,1)}.c2g-ga__nums{display:flex;align-items:baseline;gap:2px}.c2g-ga__completed{font-size:1rem;font-weight:900;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));font-variant-numeric:tabular-nums}.c2g-ga__total{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));font-variant-numeric:tabular-nums}\n"] }]
|
|
2725
|
+
}], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }] } });
|
|
2726
|
+
|
|
2727
|
+
class GearSharingWidgetComponent {
|
|
2728
|
+
data = input.required(...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
2729
|
+
displayPct = signal(0, ...(ngDevMode ? [{ debugName: "displayPct" }] : []));
|
|
2730
|
+
raf = 0;
|
|
2731
|
+
constructor() {
|
|
2732
|
+
effect(() => {
|
|
2733
|
+
const target = this.sharingPct();
|
|
2734
|
+
cancelAnimationFrame(this.raf);
|
|
2735
|
+
const start = performance.now();
|
|
2736
|
+
const tick = (now) => {
|
|
2737
|
+
const t = Math.min((now - start) / 1000, 1);
|
|
2738
|
+
const e = 1 - Math.pow(1 - t, 3);
|
|
2739
|
+
this.displayPct.set(Math.round(e * target));
|
|
2740
|
+
if (t < 1)
|
|
2741
|
+
this.raf = requestAnimationFrame(tick);
|
|
2742
|
+
};
|
|
2743
|
+
this.raf = requestAnimationFrame(tick);
|
|
2744
|
+
});
|
|
2745
|
+
}
|
|
2746
|
+
sharingPct = computed(() => {
|
|
2747
|
+
const d = this.data();
|
|
2748
|
+
if (!d.totalItems)
|
|
2749
|
+
return 0;
|
|
2750
|
+
return Math.round((d.sharedItems / d.totalItems) * 100);
|
|
2751
|
+
}, ...(ngDevMode ? [{ debugName: "sharingPct" }] : []));
|
|
2752
|
+
tier = computed(() => {
|
|
2753
|
+
const p = this.sharingPct();
|
|
2754
|
+
if (p >= 40)
|
|
2755
|
+
return 'high';
|
|
2756
|
+
if (p >= 20)
|
|
2757
|
+
return 'medium';
|
|
2758
|
+
return 'low';
|
|
2759
|
+
}, ...(ngDevMode ? [{ debugName: "tier" }] : []));
|
|
2760
|
+
arcPath = computed(() => {
|
|
2761
|
+
const pct = Math.min(this.sharingPct() / 100, 1);
|
|
2762
|
+
const r = 44, cx = 52, cy = 52;
|
|
2763
|
+
if (pct >= 0.999)
|
|
2764
|
+
return `M ${cx} ${cy - r} A ${r} ${r} 0 1 1 ${cx - 0.01} ${cy - r}`;
|
|
2765
|
+
const angle = pct * 360;
|
|
2766
|
+
const rad = ((angle - 90) * Math.PI) / 180;
|
|
2767
|
+
const x = cx + r * Math.cos(rad);
|
|
2768
|
+
const y = cy + r * Math.sin(rad);
|
|
2769
|
+
const large = angle > 180 ? 1 : 0;
|
|
2770
|
+
return `M ${cx} ${cy - r} A ${r} ${r} 0 ${large} 1 ${x} ${y}`;
|
|
2771
|
+
}, ...(ngDevMode ? [{ debugName: "arcPath" }] : []));
|
|
2772
|
+
ngOnDestroy() { cancelAnimationFrame(this.raf); }
|
|
2773
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GearSharingWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2774
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: GearSharingWidgetComponent, isStandalone: true, selector: "c2g-gear-sharing-widget", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<article class=\"c2g-gs\" [class]=\"'c2g-gs--' + tier()\">\n <header class=\"c2g-gs__header\">\n <span class=\"c2g-gs__icon\">\uD83E\uDD1D</span>\n <div class=\"c2g-gs__titles\">\n <h3 class=\"c2g-gs__title\">Gear-Sharing</h3>\n <span class=\"c2g-gs__sub\">{{ data().memberCount }} Mitglieder</span>\n </div>\n </header>\n\n <div class=\"c2g-gs__body\">\n <div class=\"c2g-gs__circle-wrap\" aria-hidden=\"true\">\n <svg class=\"c2g-gs__circle\" viewBox=\"0 0 104 104\">\n <circle cx=\"52\" cy=\"52\" r=\"44\" class=\"c2g-gs__track\" fill=\"none\" stroke-width=\"10\"/>\n <path class=\"c2g-gs__arc\" [attr.d]=\"arcPath()\" fill=\"none\" stroke-width=\"10\" stroke-linecap=\"round\"/>\n </svg>\n <div class=\"c2g-gs__center\">\n <span class=\"c2g-gs__pct\">{{ displayPct() }}%</span>\n <span class=\"c2g-gs__pct-label\">geteilt</span>\n </div>\n </div>\n\n <div class=\"c2g-gs__stats\">\n <div class=\"c2g-gs__stat\">\n <span class=\"c2g-gs__stat-val\">{{ data().sharedItems }}</span>\n <span class=\"c2g-gs__stat-label\">geteilt</span>\n </div>\n <div class=\"c2g-gs__stat\">\n <span class=\"c2g-gs__stat-val\">{{ data().totalItems - data().sharedItems }}</span>\n <span class=\"c2g-gs__stat-label\">eigene</span>\n </div>\n @if (data().savedWeightPerPersonG) {\n <div class=\"c2g-gs__stat c2g-gs__stat--highlight\">\n <span class=\"c2g-gs__stat-val\">{{ (data().savedWeightPerPersonG! / 1000) | number:'1.1-1' }} kg</span>\n <span class=\"c2g-gs__stat-label\">gespart/Person</span>\n </div>\n }\n </div>\n </div>\n</article>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-gs{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));border-radius:var(--c2g-radius-xl, 20px);padding:18px 20px 16px;display:flex;flex-direction:column;gap:14px;transition:box-shadow .2s ease,transform .2s ease}.c2g-gs:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .11));transform:translateY(-2px)}.c2g-gs--high{--gs-color: #22c55e}.c2g-gs--medium{--gs-color: #f59e0b}.c2g-gs--low{--gs-color: #94a3b8}.c2g-gs__header{display:flex;align-items:center;gap:8px}.c2g-gs__icon{font-size:1.25rem;line-height:1}.c2g-gs__titles{display:flex;flex-direction:column;gap:2px}.c2g-gs__title{margin:0;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-gs__sub{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));opacity:.7}.c2g-gs__body{display:flex;gap:16px;align-items:center}.c2g-gs__circle-wrap{position:relative;width:104px;height:104px;flex-shrink:0}.c2g-gs__circle{width:100%;height:100%;display:block}.c2g-gs__track{stroke:var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant))}.c2g-gs__arc{stroke:var(--gs-color, var(--c2g-theme-primary, var(--c2g-color-primary)));transition:d .9s cubic-bezier(.4,0,.2,1)}.c2g-gs__center{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center}.c2g-gs__pct{font-size:1.75rem;font-weight:900;color:var(--gs-color, var(--c2g-theme-primary, var(--c2g-color-primary)));font-variant-numeric:tabular-nums;line-height:1}.c2g-gs__pct-label{font-size:.6rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-gs__stats{flex:1;display:flex;flex-direction:column;gap:8px}.c2g-gs__stat{display:flex;flex-direction:column;gap:1px}.c2g-gs__stat-val{font-size:1.125rem;font-weight:800;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));font-variant-numeric:tabular-nums;line-height:1}.c2g-gs__stat-label{font-size:.65rem;text-transform:uppercase;letter-spacing:.06em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-gs__stat--highlight .c2g-gs__stat-val{color:var(--gs-color, var(--c2g-theme-primary, var(--c2g-color-primary)))}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1$2.DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2775
|
+
}
|
|
2776
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: GearSharingWidgetComponent, decorators: [{
|
|
2777
|
+
type: Component,
|
|
2778
|
+
args: [{ selector: 'c2g-gear-sharing-widget', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<article class=\"c2g-gs\" [class]=\"'c2g-gs--' + tier()\">\n <header class=\"c2g-gs__header\">\n <span class=\"c2g-gs__icon\">\uD83E\uDD1D</span>\n <div class=\"c2g-gs__titles\">\n <h3 class=\"c2g-gs__title\">Gear-Sharing</h3>\n <span class=\"c2g-gs__sub\">{{ data().memberCount }} Mitglieder</span>\n </div>\n </header>\n\n <div class=\"c2g-gs__body\">\n <div class=\"c2g-gs__circle-wrap\" aria-hidden=\"true\">\n <svg class=\"c2g-gs__circle\" viewBox=\"0 0 104 104\">\n <circle cx=\"52\" cy=\"52\" r=\"44\" class=\"c2g-gs__track\" fill=\"none\" stroke-width=\"10\"/>\n <path class=\"c2g-gs__arc\" [attr.d]=\"arcPath()\" fill=\"none\" stroke-width=\"10\" stroke-linecap=\"round\"/>\n </svg>\n <div class=\"c2g-gs__center\">\n <span class=\"c2g-gs__pct\">{{ displayPct() }}%</span>\n <span class=\"c2g-gs__pct-label\">geteilt</span>\n </div>\n </div>\n\n <div class=\"c2g-gs__stats\">\n <div class=\"c2g-gs__stat\">\n <span class=\"c2g-gs__stat-val\">{{ data().sharedItems }}</span>\n <span class=\"c2g-gs__stat-label\">geteilt</span>\n </div>\n <div class=\"c2g-gs__stat\">\n <span class=\"c2g-gs__stat-val\">{{ data().totalItems - data().sharedItems }}</span>\n <span class=\"c2g-gs__stat-label\">eigene</span>\n </div>\n @if (data().savedWeightPerPersonG) {\n <div class=\"c2g-gs__stat c2g-gs__stat--highlight\">\n <span class=\"c2g-gs__stat-val\">{{ (data().savedWeightPerPersonG! / 1000) | number:'1.1-1' }} kg</span>\n <span class=\"c2g-gs__stat-label\">gespart/Person</span>\n </div>\n }\n </div>\n </div>\n</article>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-gs{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));border-radius:var(--c2g-radius-xl, 20px);padding:18px 20px 16px;display:flex;flex-direction:column;gap:14px;transition:box-shadow .2s ease,transform .2s ease}.c2g-gs:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .11));transform:translateY(-2px)}.c2g-gs--high{--gs-color: #22c55e}.c2g-gs--medium{--gs-color: #f59e0b}.c2g-gs--low{--gs-color: #94a3b8}.c2g-gs__header{display:flex;align-items:center;gap:8px}.c2g-gs__icon{font-size:1.25rem;line-height:1}.c2g-gs__titles{display:flex;flex-direction:column;gap:2px}.c2g-gs__title{margin:0;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-gs__sub{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));opacity:.7}.c2g-gs__body{display:flex;gap:16px;align-items:center}.c2g-gs__circle-wrap{position:relative;width:104px;height:104px;flex-shrink:0}.c2g-gs__circle{width:100%;height:100%;display:block}.c2g-gs__track{stroke:var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant))}.c2g-gs__arc{stroke:var(--gs-color, var(--c2g-theme-primary, var(--c2g-color-primary)));transition:d .9s cubic-bezier(.4,0,.2,1)}.c2g-gs__center{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center}.c2g-gs__pct{font-size:1.75rem;font-weight:900;color:var(--gs-color, var(--c2g-theme-primary, var(--c2g-color-primary)));font-variant-numeric:tabular-nums;line-height:1}.c2g-gs__pct-label{font-size:.6rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-gs__stats{flex:1;display:flex;flex-direction:column;gap:8px}.c2g-gs__stat{display:flex;flex-direction:column;gap:1px}.c2g-gs__stat-val{font-size:1.125rem;font-weight:800;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));font-variant-numeric:tabular-nums;line-height:1}.c2g-gs__stat-label{font-size:.65rem;text-transform:uppercase;letter-spacing:.06em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-gs__stat--highlight .c2g-gs__stat-val{color:var(--gs-color, var(--c2g-theme-primary, var(--c2g-color-primary)))}\n"] }]
|
|
2779
|
+
}], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }] } });
|
|
2780
|
+
|
|
2781
|
+
class AdventureRadiusWidgetComponent {
|
|
2782
|
+
data = input.required(...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
2783
|
+
buckets = computed(() => this.data().buckets ?? [], ...(ngDevMode ? [{ debugName: "buckets" }] : []));
|
|
2784
|
+
maxBucketCount = computed(() => Math.max(...this.buckets().map(b => b.count), 1), ...(ngDevMode ? [{ debugName: "maxBucketCount" }] : []));
|
|
2785
|
+
bucketPct(count) {
|
|
2786
|
+
return Math.round((count / this.maxBucketCount()) * 100);
|
|
2787
|
+
}
|
|
2788
|
+
radiusTier = computed(() => {
|
|
2789
|
+
const max = this.data().maxRadiusKm;
|
|
2790
|
+
if (max >= 1000)
|
|
2791
|
+
return 'international';
|
|
2792
|
+
if (max >= 300)
|
|
2793
|
+
return 'national';
|
|
2794
|
+
if (max >= 100)
|
|
2795
|
+
return 'regional';
|
|
2796
|
+
return 'local';
|
|
2797
|
+
}, ...(ngDevMode ? [{ debugName: "radiusTier" }] : []));
|
|
2798
|
+
tierEmoji = computed(() => {
|
|
2799
|
+
switch (this.radiusTier()) {
|
|
2800
|
+
case 'international': return '🌍';
|
|
2801
|
+
case 'national': return '🗺️';
|
|
2802
|
+
case 'regional': return '🏔️';
|
|
2803
|
+
default: return '🏡';
|
|
2804
|
+
}
|
|
2805
|
+
}, ...(ngDevMode ? [{ debugName: "tierEmoji" }] : []));
|
|
2806
|
+
tierLabel = computed(() => {
|
|
2807
|
+
switch (this.radiusTier()) {
|
|
2808
|
+
case 'international': return 'Weltenbummler';
|
|
2809
|
+
case 'national': return 'Deutschlandreisender';
|
|
2810
|
+
case 'regional': return 'Regionalentdecker';
|
|
2811
|
+
default: return 'Heimatverbunden';
|
|
2812
|
+
}
|
|
2813
|
+
}, ...(ngDevMode ? [{ debugName: "tierLabel" }] : []));
|
|
2814
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AdventureRadiusWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2815
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: AdventureRadiusWidgetComponent, isStandalone: true, selector: "c2g-adventure-radius-widget", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<article class=\"c2g-ar\">\n <header class=\"c2g-ar__header\">\n <span class=\"c2g-ar__icon\">{{ tierEmoji() }}</span>\n <div class=\"c2g-ar__titles\">\n <h3 class=\"c2g-ar__title\">Abenteuer-Radius</h3>\n @if (data().homeLabel) {\n <span class=\"c2g-ar__sub\">ab {{ data().homeLabel }}</span>\n }\n </div>\n <span class=\"c2g-ar__tier-badge c2g-ar__tier-badge--{{ radiusTier() }}\">\n {{ tierLabel() }}\n </span>\n </header>\n\n <div class=\"c2g-ar__hero\">\n <div class=\"c2g-ar__stat\">\n <span class=\"c2g-ar__val\">{{ data().avgRadiusKm | number:'1.0-0' }}</span>\n <span class=\"c2g-ar__unit\">km</span>\n <span class=\"c2g-ar__label\">\u00D8 Radius</span>\n </div>\n <div class=\"c2g-ar__divider\"></div>\n <div class=\"c2g-ar__stat\">\n <span class=\"c2g-ar__val\">{{ data().maxRadiusKm | number:'1.0-0' }}</span>\n <span class=\"c2g-ar__unit\">km</span>\n <span class=\"c2g-ar__label\">Maximum</span>\n </div>\n </div>\n\n @if (data().maxTourName) {\n <div class=\"c2g-ar__max-tour\">\n <span class=\"c2g-ar__max-icon\">\uD83D\uDCCD</span>\n <span class=\"c2g-ar__max-name\">{{ data().maxTourName }}</span>\n </div>\n }\n\n @if (buckets().length) {\n <div class=\"c2g-ar__buckets\">\n @for (b of buckets(); track b.labelKm) {\n <div class=\"c2g-ar__bucket\">\n <div class=\"c2g-ar__bucket-bar-wrap\">\n <div class=\"c2g-ar__bucket-bar\" [style.height.%]=\"bucketPct(b.count)\"></div>\n </div>\n <span class=\"c2g-ar__bucket-label\">{{ b.labelKm }}</span>\n <span class=\"c2g-ar__bucket-count\">{{ b.count }}</span>\n </div>\n }\n </div>\n }\n</article>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-ar{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));border-radius:var(--c2g-radius-xl, 20px);padding:18px 20px 16px;display:flex;flex-direction:column;gap:12px;transition:box-shadow .2s ease,transform .2s ease}.c2g-ar:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .11));transform:translateY(-2px)}.c2g-ar__header{display:flex;align-items:center;gap:8px}.c2g-ar__icon{font-size:1.5rem;line-height:1}.c2g-ar__titles{flex:1;display:flex;flex-direction:column;gap:2px}.c2g-ar__title{margin:0;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-ar__sub{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));opacity:.7}.c2g-ar__tier-badge{font-size:.7rem;font-weight:700;padding:3px 8px;border-radius:999px;white-space:nowrap}.c2g-ar__tier-badge--international{color:#a855f7;background:#a855f71a}.c2g-ar__tier-badge--national{color:#3b82f6;background:#3b82f61a}.c2g-ar__tier-badge--regional{color:#22c55e;background:#22c55e1a}.c2g-ar__tier-badge--local{color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));background:var(--c2g-theme-surface-container, var(--c2g-color-bg-secondary))}.c2g-ar__hero{display:flex;align-items:center;gap:0}.c2g-ar__stat{flex:1;display:flex;flex-direction:column;align-items:center;gap:2px}.c2g-ar__val{font-size:2.25rem;font-weight:800;line-height:1;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));font-variant-numeric:tabular-nums;letter-spacing:-.02em}.c2g-ar__unit{font-size:.875rem;font-weight:700;color:var(--c2g-theme-primary, var(--c2g-color-primary))}.c2g-ar__label{font-size:.65rem;text-transform:uppercase;letter-spacing:.07em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-ar__divider{width:1px;height:48px;background:var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant))}.c2g-ar__max-tour{display:flex;align-items:center;gap:6px;padding:6px 10px;background:var(--c2g-theme-surface-container, var(--c2g-color-bg-secondary));border-radius:var(--c2g-radius-md, 10px)}.c2g-ar__max-icon{font-size:.875rem}.c2g-ar__max-name{font-size:.8rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c2g-ar__buckets{display:flex;align-items:flex-end;gap:6px;height:64px;padding-top:4px;border-top:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant))}.c2g-ar__bucket{flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;height:100%;justify-content:flex-end}.c2g-ar__bucket-bar-wrap{flex:1;width:100%;display:flex;align-items:flex-end}.c2g-ar__bucket-bar{width:100%;border-radius:3px 3px 0 0;min-height:3px;background:var(--c2g-theme-primary, var(--c2g-color-primary));transition:height .7s cubic-bezier(.4,0,.2,1);opacity:.75}.c2g-ar__bucket-label{font-size:.55rem;font-weight:700;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));text-align:center;white-space:nowrap}.c2g-ar__bucket-count{font-size:.6rem;font-weight:800;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1$2.DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2816
|
+
}
|
|
2817
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AdventureRadiusWidgetComponent, decorators: [{
|
|
2818
|
+
type: Component,
|
|
2819
|
+
args: [{ selector: 'c2g-adventure-radius-widget', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<article class=\"c2g-ar\">\n <header class=\"c2g-ar__header\">\n <span class=\"c2g-ar__icon\">{{ tierEmoji() }}</span>\n <div class=\"c2g-ar__titles\">\n <h3 class=\"c2g-ar__title\">Abenteuer-Radius</h3>\n @if (data().homeLabel) {\n <span class=\"c2g-ar__sub\">ab {{ data().homeLabel }}</span>\n }\n </div>\n <span class=\"c2g-ar__tier-badge c2g-ar__tier-badge--{{ radiusTier() }}\">\n {{ tierLabel() }}\n </span>\n </header>\n\n <div class=\"c2g-ar__hero\">\n <div class=\"c2g-ar__stat\">\n <span class=\"c2g-ar__val\">{{ data().avgRadiusKm | number:'1.0-0' }}</span>\n <span class=\"c2g-ar__unit\">km</span>\n <span class=\"c2g-ar__label\">\u00D8 Radius</span>\n </div>\n <div class=\"c2g-ar__divider\"></div>\n <div class=\"c2g-ar__stat\">\n <span class=\"c2g-ar__val\">{{ data().maxRadiusKm | number:'1.0-0' }}</span>\n <span class=\"c2g-ar__unit\">km</span>\n <span class=\"c2g-ar__label\">Maximum</span>\n </div>\n </div>\n\n @if (data().maxTourName) {\n <div class=\"c2g-ar__max-tour\">\n <span class=\"c2g-ar__max-icon\">\uD83D\uDCCD</span>\n <span class=\"c2g-ar__max-name\">{{ data().maxTourName }}</span>\n </div>\n }\n\n @if (buckets().length) {\n <div class=\"c2g-ar__buckets\">\n @for (b of buckets(); track b.labelKm) {\n <div class=\"c2g-ar__bucket\">\n <div class=\"c2g-ar__bucket-bar-wrap\">\n <div class=\"c2g-ar__bucket-bar\" [style.height.%]=\"bucketPct(b.count)\"></div>\n </div>\n <span class=\"c2g-ar__bucket-label\">{{ b.labelKm }}</span>\n <span class=\"c2g-ar__bucket-count\">{{ b.count }}</span>\n </div>\n }\n </div>\n }\n</article>\n", styles: [":host{display:block;font-family:var(--c2g-font-family-base, \"Quicksand\", sans-serif)}.c2g-ar{background:var(--c2g-theme-surface, var(--c2g-color-surface));border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));box-shadow:var(--c2g-shadow-sm, 0 1px 4px rgba(0, 0, 0, .06));border-radius:var(--c2g-radius-xl, 20px);padding:18px 20px 16px;display:flex;flex-direction:column;gap:12px;transition:box-shadow .2s ease,transform .2s ease}.c2g-ar:hover{box-shadow:var(--c2g-shadow-lg, 0 8px 28px rgba(0, 0, 0, .11));transform:translateY(-2px)}.c2g-ar__header{display:flex;align-items:center;gap:8px}.c2g-ar__icon{font-size:1.5rem;line-height:1}.c2g-ar__titles{flex:1;display:flex;flex-direction:column;gap:2px}.c2g-ar__title{margin:0;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.09em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-ar__sub{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));opacity:.7}.c2g-ar__tier-badge{font-size:.7rem;font-weight:700;padding:3px 8px;border-radius:999px;white-space:nowrap}.c2g-ar__tier-badge--international{color:#a855f7;background:#a855f71a}.c2g-ar__tier-badge--national{color:#3b82f6;background:#3b82f61a}.c2g-ar__tier-badge--regional{color:#22c55e;background:#22c55e1a}.c2g-ar__tier-badge--local{color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));background:var(--c2g-theme-surface-container, var(--c2g-color-bg-secondary))}.c2g-ar__hero{display:flex;align-items:center;gap:0}.c2g-ar__stat{flex:1;display:flex;flex-direction:column;align-items:center;gap:2px}.c2g-ar__val{font-size:2.25rem;font-weight:800;line-height:1;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));font-variant-numeric:tabular-nums;letter-spacing:-.02em}.c2g-ar__unit{font-size:.875rem;font-weight:700;color:var(--c2g-theme-primary, var(--c2g-color-primary))}.c2g-ar__label{font-size:.65rem;text-transform:uppercase;letter-spacing:.07em;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-ar__divider{width:1px;height:48px;background:var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant))}.c2g-ar__max-tour{display:flex;align-items:center;gap:6px;padding:6px 10px;background:var(--c2g-theme-surface-container, var(--c2g-color-bg-secondary));border-radius:var(--c2g-radius-md, 10px)}.c2g-ar__max-icon{font-size:.875rem}.c2g-ar__max-name{font-size:.8rem;font-weight:600;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.c2g-ar__buckets{display:flex;align-items:flex-end;gap:6px;height:64px;padding-top:4px;border-top:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant))}.c2g-ar__bucket{flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;height:100%;justify-content:flex-end}.c2g-ar__bucket-bar-wrap{flex:1;width:100%;display:flex;align-items:flex-end}.c2g-ar__bucket-bar{width:100%;border-radius:3px 3px 0 0;min-height:3px;background:var(--c2g-theme-primary, var(--c2g-color-primary));transition:height .7s cubic-bezier(.4,0,.2,1);opacity:.75}.c2g-ar__bucket-label{font-size:.55rem;font-weight:700;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted));text-align:center;white-space:nowrap}.c2g-ar__bucket-count{font-size:.6rem;font-weight:800;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary))}\n"] }]
|
|
2820
|
+
}], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }] } });
|
|
2821
|
+
|
|
2822
|
+
class PackProgressWidgetComponent {
|
|
2823
|
+
data = input.required(...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
2824
|
+
rows = computed(() => this.data().rows, ...(ngDevMode ? [{ debugName: "rows" }] : []));
|
|
2825
|
+
overallPacked = computed(() => this.rows().reduce((s, r) => s + r.packed, 0), ...(ngDevMode ? [{ debugName: "overallPacked" }] : []));
|
|
2826
|
+
overallTotal = computed(() => this.rows().reduce((s, r) => s + r.total, 0), ...(ngDevMode ? [{ debugName: "overallTotal" }] : []));
|
|
2827
|
+
overallPercent = computed(() => {
|
|
2828
|
+
const t = this.overallTotal();
|
|
2829
|
+
return t === 0 ? 0 : Math.round((this.overallPacked() / t) * 100);
|
|
2830
|
+
}, ...(ngDevMode ? [{ debugName: "overallPercent" }] : []));
|
|
2831
|
+
pct(row) {
|
|
2832
|
+
return row.total === 0 ? 0 : Math.round((row.packed / row.total) * 100);
|
|
2833
|
+
}
|
|
2834
|
+
tone(row) {
|
|
2835
|
+
const p = this.pct(row);
|
|
2836
|
+
if (p === 100)
|
|
2837
|
+
return 'success';
|
|
2838
|
+
if (p >= 50)
|
|
2839
|
+
return 'warning';
|
|
2840
|
+
return 'danger';
|
|
2841
|
+
}
|
|
2842
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: PackProgressWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2843
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: PackProgressWidgetComponent, isStandalone: true, selector: "c2g-pack-progress-widget", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"c2g-pp\">\n\n <div class=\"c2g-pp__header\">\n <span class=\"c2g-pp__title\">\uD83D\uDCE6 Packfortschritt</span>\n @if (data().tourName) {\n <span class=\"c2g-pp__tour\">{{ data().tourName }}</span>\n }\n <span class=\"c2g-pp__overall\">{{ overallPacked() }}/{{ overallTotal() }}</span>\n </div>\n\n <div class=\"c2g-pp__rows\">\n @for (row of rows(); track row.label) {\n <div class=\"c2g-pp__row\" [class.c2g-pp__row--highlight]=\"row.highlight\">\n\n <div class=\"c2g-pp__row-label\">\n <span class=\"c2g-pp__row-icon\" aria-hidden=\"true\">{{ row.icon }}</span>\n <span class=\"c2g-pp__row-name\">{{ row.label }}</span>\n </div>\n\n <div class=\"c2g-pp__bar-wrap\" [attr.aria-label]=\"row.label + ': ' + row.packed + ' von ' + row.total + ' gepackt'\">\n <div\n class=\"c2g-pp__bar-fill\"\n [class.c2g-pp__bar-fill--success]=\"tone(row) === 'success'\"\n [class.c2g-pp__bar-fill--warning]=\"tone(row) === 'warning'\"\n [class.c2g-pp__bar-fill--danger]=\"tone(row) === 'danger'\"\n [style.width.%]=\"pct(row)\">\n </div>\n </div>\n\n <span\n class=\"c2g-pp__count\"\n [class.c2g-pp__count--success]=\"tone(row) === 'success'\"\n [class.c2g-pp__count--warning]=\"tone(row) === 'warning'\"\n [class.c2g-pp__count--danger]=\"tone(row) === 'danger'\">\n {{ row.packed }}/{{ row.total }}\n </span>\n\n </div>\n }\n </div>\n\n <!-- Overall strip -->\n <div class=\"c2g-pp__strip-track\">\n <div class=\"c2g-pp__strip-fill\" [style.width.%]=\"overallPercent()\"></div>\n </div>\n\n</div>\n", styles: [".c2g-pp{border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));border-radius:1rem;background:var(--c2g-theme-surface, var(--c2g-color-surface));overflow:hidden;display:grid;gap:0;transition:background var(--c2g-transition-medium, .25s ease),border-color var(--c2g-transition-medium, .25s ease)}.c2g-pp__header{display:flex;align-items:center;gap:.5rem;padding:.85rem 1rem .7rem}.c2g-pp__title{font-weight:700;font-size:.9rem;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));flex:1}.c2g-pp__tour{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-pp__overall{font-size:.78rem;font-weight:600;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-secondary));background:var(--c2g-theme-surface-container, var(--c2g-color-neutral-100));border-radius:99px;padding:.1rem .5rem}.c2g-pp__rows{display:grid;gap:0;padding:0 1rem .75rem}.c2g-pp__row{display:grid;grid-template-columns:7rem 1fr 2.8rem;align-items:center;gap:.65rem;padding:.45rem 0;border-bottom:1px solid var(--c2g-color-outline-variant)}.c2g-pp__row:last-child{border-bottom:0}.c2g-pp__row--highlight{background:color-mix(in srgb,#f59e0b 5%,transparent);border-radius:.5rem;margin:0 -.5rem;padding:.45rem .5rem}.c2g-pp__row--highlight .c2g-pp__row-name{font-weight:600}.c2g-pp__row-label{display:flex;align-items:center;gap:.4rem;min-width:0}.c2g-pp__row-icon{font-size:1rem;line-height:1;flex-shrink:0}.c2g-pp__row-name{font-size:.82rem;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.c2g-pp__bar-wrap{height:.45rem;border-radius:99px;background:var(--c2g-theme-surface-container, var(--c2g-color-neutral-100));overflow:hidden}.c2g-pp__bar-fill{height:100%;border-radius:99px;transition:width .6s cubic-bezier(.4,0,.2,1)}.c2g-pp__bar-fill--success{background:#16a34a}.c2g-pp__bar-fill--warning{background:#f59e0b}.c2g-pp__bar-fill--danger{background:#ef4444}.c2g-pp__count{font-size:.75rem;font-weight:700;text-align:right;white-space:nowrap}.c2g-pp__count--success{color:#16a34a}.c2g-pp__count--warning{color:#d97706}.c2g-pp__count--danger{color:#dc2626}.c2g-pp__strip-track{height:3px;background:var(--c2g-theme-surface-container, var(--c2g-color-neutral-100))}.c2g-pp__strip-fill{height:100%;background:var(--c2g-color-secondary-dark, #2d6a4f);transition:width .7s cubic-bezier(.4,0,.2,1)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2844
|
+
}
|
|
2845
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: PackProgressWidgetComponent, decorators: [{
|
|
2846
|
+
type: Component,
|
|
2847
|
+
args: [{ selector: 'c2g-pack-progress-widget', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"c2g-pp\">\n\n <div class=\"c2g-pp__header\">\n <span class=\"c2g-pp__title\">\uD83D\uDCE6 Packfortschritt</span>\n @if (data().tourName) {\n <span class=\"c2g-pp__tour\">{{ data().tourName }}</span>\n }\n <span class=\"c2g-pp__overall\">{{ overallPacked() }}/{{ overallTotal() }}</span>\n </div>\n\n <div class=\"c2g-pp__rows\">\n @for (row of rows(); track row.label) {\n <div class=\"c2g-pp__row\" [class.c2g-pp__row--highlight]=\"row.highlight\">\n\n <div class=\"c2g-pp__row-label\">\n <span class=\"c2g-pp__row-icon\" aria-hidden=\"true\">{{ row.icon }}</span>\n <span class=\"c2g-pp__row-name\">{{ row.label }}</span>\n </div>\n\n <div class=\"c2g-pp__bar-wrap\" [attr.aria-label]=\"row.label + ': ' + row.packed + ' von ' + row.total + ' gepackt'\">\n <div\n class=\"c2g-pp__bar-fill\"\n [class.c2g-pp__bar-fill--success]=\"tone(row) === 'success'\"\n [class.c2g-pp__bar-fill--warning]=\"tone(row) === 'warning'\"\n [class.c2g-pp__bar-fill--danger]=\"tone(row) === 'danger'\"\n [style.width.%]=\"pct(row)\">\n </div>\n </div>\n\n <span\n class=\"c2g-pp__count\"\n [class.c2g-pp__count--success]=\"tone(row) === 'success'\"\n [class.c2g-pp__count--warning]=\"tone(row) === 'warning'\"\n [class.c2g-pp__count--danger]=\"tone(row) === 'danger'\">\n {{ row.packed }}/{{ row.total }}\n </span>\n\n </div>\n }\n </div>\n\n <!-- Overall strip -->\n <div class=\"c2g-pp__strip-track\">\n <div class=\"c2g-pp__strip-fill\" [style.width.%]=\"overallPercent()\"></div>\n </div>\n\n</div>\n", styles: [".c2g-pp{border:1px solid var(--c2g-theme-outline-variant, var(--c2g-color-outline-variant));border-radius:1rem;background:var(--c2g-theme-surface, var(--c2g-color-surface));overflow:hidden;display:grid;gap:0;transition:background var(--c2g-transition-medium, .25s ease),border-color var(--c2g-transition-medium, .25s ease)}.c2g-pp__header{display:flex;align-items:center;gap:.5rem;padding:.85rem 1rem .7rem}.c2g-pp__title{font-weight:700;font-size:.9rem;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));flex:1}.c2g-pp__tour{font-size:.75rem;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-muted))}.c2g-pp__overall{font-size:.78rem;font-weight:600;color:var(--c2g-theme-on-surface-variant, var(--c2g-color-text-secondary));background:var(--c2g-theme-surface-container, var(--c2g-color-neutral-100));border-radius:99px;padding:.1rem .5rem}.c2g-pp__rows{display:grid;gap:0;padding:0 1rem .75rem}.c2g-pp__row{display:grid;grid-template-columns:7rem 1fr 2.8rem;align-items:center;gap:.65rem;padding:.45rem 0;border-bottom:1px solid var(--c2g-color-outline-variant)}.c2g-pp__row:last-child{border-bottom:0}.c2g-pp__row--highlight{background:color-mix(in srgb,#f59e0b 5%,transparent);border-radius:.5rem;margin:0 -.5rem;padding:.45rem .5rem}.c2g-pp__row--highlight .c2g-pp__row-name{font-weight:600}.c2g-pp__row-label{display:flex;align-items:center;gap:.4rem;min-width:0}.c2g-pp__row-icon{font-size:1rem;line-height:1;flex-shrink:0}.c2g-pp__row-name{font-size:.82rem;color:var(--c2g-theme-on-surface, var(--c2g-color-text-primary));white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.c2g-pp__bar-wrap{height:.45rem;border-radius:99px;background:var(--c2g-theme-surface-container, var(--c2g-color-neutral-100));overflow:hidden}.c2g-pp__bar-fill{height:100%;border-radius:99px;transition:width .6s cubic-bezier(.4,0,.2,1)}.c2g-pp__bar-fill--success{background:#16a34a}.c2g-pp__bar-fill--warning{background:#f59e0b}.c2g-pp__bar-fill--danger{background:#ef4444}.c2g-pp__count{font-size:.75rem;font-weight:700;text-align:right;white-space:nowrap}.c2g-pp__count--success{color:#16a34a}.c2g-pp__count--warning{color:#d97706}.c2g-pp__count--danger{color:#dc2626}.c2g-pp__strip-track{height:3px;background:var(--c2g-theme-surface-container, var(--c2g-color-neutral-100))}.c2g-pp__strip-fill{height:100%;background:var(--c2g-color-secondary-dark, #2d6a4f);transition:width .7s cubic-bezier(.4,0,.2,1)}\n"] }]
|
|
2848
|
+
}], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }] } });
|
|
2849
|
+
|
|
2850
|
+
/**
|
|
2851
|
+
* Generated bundle index. Do not edit.
|
|
2852
|
+
*/
|
|
2853
|
+
|
|
2854
|
+
export { ActionMenuComponent, AdventureRadiusWidgetComponent, AdventureScoreWidgetComponent, C2G_PACKING_CATEGORY_INFO, CampingScoreWidgetComponent, CriticalItemsAlertWidgetComponent, DEFAULT_PACKING_LIST_CONFIG, DEFAULT_PACKING_LIST_LABEL_KEYS, GearSharingWidgetComponent, GearValueWidgetComponent, GroupActivityWidgetComponent, GroupCompositionWidgetComponent, MainNavigationComponent, MainNavigationItemComponent, MemberItemComponent, MemberListComponent, MemberPanelComponent, MemberReadinessWidgetComponent, MemberTagsComponent, MenuComponent, NextAdventureWidgetComponent, PackProgressWidgetComponent, PackStatusWidgetComponent, PackWeightWidgetComponent, PackingListCategoryComponent, PackingListComponent, PackingListFiltersComponent, PackingListItemComponent, PackingListItemCreateComponent, PackingListPrivateListComponent, PackingListStatsComponent, RainVisualizationWidgetComponent, SeasonDnaWidgetComponent, StreakWidgetComponent, SubmenuComponent, SubmenuItemComponent, TopGearWidgetComponent, TotalKmWidgetComponent, TourRhythmWidgetComponent, TourTypeSplitWidgetComponent, WEATHER_ICON_MAP, WeatherWidgetComponent, WeightBreakdownWidgetComponent, WeightHistoryWidgetComponent, WindIndicatorWidgetComponent, resolvePackingCategory, resolveWeatherIcon };
|
|
2855
|
+
//# sourceMappingURL=camp2gether-c2g-ui-presets.mjs.map
|