@propbinder/mobile-design 0.2.48 → 0.2.50

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.
Files changed (221) hide show
  1. package/ng-package.json +24 -0
  2. package/package.json +3 -39
  3. package/src/animations/page-transitions.ts +165 -0
  4. package/src/assets/fonts/brockmann-mediumitalic-webfont.woff2 +0 -0
  5. package/src/assets/fonts/brockmann-regularitalic-webfont.woff2 +0 -0
  6. package/src/assets/fonts/brockmann-semibolditalic-webfont.woff2 +0 -0
  7. package/src/components/action-list-item/ds-mobile-action-list-item.ts +102 -0
  8. package/src/components/action-list-item/index.ts +2 -0
  9. package/src/components/app-icon/ds-app-icon.ts +133 -0
  10. package/src/components/app-icon/index.ts +2 -0
  11. package/src/components/attachment-preview/ds-mobile-attachment-preview.css +139 -0
  12. package/src/components/attachment-preview/ds-mobile-attachment-preview.ts +164 -0
  13. package/src/components/attachment-preview/index.ts +1 -0
  14. package/src/components/avatar-with-badge/ds-avatar-with-badge.ts +142 -0
  15. package/src/components/avatar-with-badge/index.ts +2 -0
  16. package/src/components/booking-modal/ds-mobile-booking-confirmation-wrapper.ts +71 -0
  17. package/src/components/booking-modal/ds-mobile-booking-modal.service.ts +121 -0
  18. package/src/components/booking-modal/ds-mobile-booking-modal.ts +598 -0
  19. package/src/components/booking-modal/ds-mobile-booking-summary.ts +161 -0
  20. package/src/components/booking-modal/index.ts +4 -0
  21. package/src/components/bottom-sheet/ds-mobile-actions-bottom-sheet.ts +266 -0
  22. package/src/components/bottom-sheet/ds-mobile-bottom-sheet-header.ts +146 -0
  23. package/src/components/bottom-sheet/ds-mobile-bottom-sheet-wrapper.ts +156 -0
  24. package/src/components/bottom-sheet/ds-mobile-bottom-sheet.css +101 -0
  25. package/src/components/bottom-sheet/ds-mobile-bottom-sheet.service.ts +169 -0
  26. package/src/components/bottom-sheet/ds-mobile-confirmation-sheet.ts +211 -0
  27. package/src/components/bottom-sheet/ds-mobile-post-create-bottom-sheet.ts +578 -0
  28. package/src/components/bottom-sheet/ds-mobile-profile-actions-sheet.ts +614 -0
  29. package/src/components/bottom-sheet/index.ts +8 -0
  30. package/src/components/bottom-sheet/modal-shadow-fix.ts +42 -0
  31. package/src/components/card-inline/ds-mobile-card-inline.ts +301 -0
  32. package/src/components/card-inline/index.ts +2 -0
  33. package/src/components/card-inline-banner/ds-mobile-card-inline-banner.ts +118 -0
  34. package/src/components/card-inline-banner/index.ts +1 -0
  35. package/src/components/card-inline-contact/ds-mobile-card-inline-contact.ts +120 -0
  36. package/src/components/card-inline-contact/index.ts +1 -0
  37. package/src/components/card-inline-file/ds-mobile-card-inline-file.ts +141 -0
  38. package/src/components/card-inline-file/index.ts +1 -0
  39. package/src/components/chat-modal/ds-mobile-chat-modal.css +159 -0
  40. package/src/components/chat-modal/ds-mobile-chat-modal.service.ts +105 -0
  41. package/src/components/chat-modal/ds-mobile-chat-modal.ts +918 -0
  42. package/src/components/chat-modal/index.ts +8 -0
  43. package/src/components/comment/ds-mobile-comment.ts +568 -0
  44. package/src/components/comment/index.ts +2 -0
  45. package/src/components/contact-list-item/ds-mobile-contact-list-item.ts +182 -0
  46. package/src/components/contact-list-item/index.ts +2 -0
  47. package/src/components/content/ds-mobile-content.ts +139 -0
  48. package/src/components/content/index.ts +2 -0
  49. package/src/components/dropdown/ds-mobile-dropdown.css +199 -0
  50. package/src/components/dropdown/ds-mobile-dropdown.ts +340 -0
  51. package/src/components/dropdown/index.ts +2 -0
  52. package/src/components/ds-mobile-tabs.css +407 -0
  53. package/src/components/ds-mobile-tabs.ts +216 -0
  54. package/src/components/empty-state/ds-mobile-empty-state.ts +120 -0
  55. package/src/components/empty-state/index.ts +2 -0
  56. package/src/components/fab/ds-mobile-fab.ts +315 -0
  57. package/src/components/fab/index.ts +1 -0
  58. package/src/components/facility-creation-modal/ds-mobile-facility-creation-confirmation-wrapper.ts +121 -0
  59. package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.css +189 -0
  60. package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.service.ts +135 -0
  61. package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.ts +656 -0
  62. package/src/components/facility-creation-modal/index.ts +9 -0
  63. package/src/components/facility-creation-modal/sheets/ds-mobile-access-sheet.ts +105 -0
  64. package/src/components/facility-creation-modal/sheets/ds-mobile-price-sheet.ts +188 -0
  65. package/src/components/facility-creation-modal/sheets/ds-mobile-when-can-book-sheet.ts +460 -0
  66. package/src/components/facility-creation-modal/sheets/ds-mobile-who-can-book-sheet.ts +134 -0
  67. package/src/components/facility-detail-modal/ds-mobile-facility-detail-modal.service.ts +69 -0
  68. package/src/components/facility-detail-modal/ds-mobile-facility-detail-modal.ts +379 -0
  69. package/src/components/facility-detail-modal/index.ts +2 -0
  70. package/src/components/file-attachment/ds-mobile-file-attachment.ts +164 -0
  71. package/src/components/file-attachment/index.ts +2 -0
  72. package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.css +214 -0
  73. package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.service.ts +84 -0
  74. package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.ts +424 -0
  75. package/src/components/handbook-detail-modal/index.ts +3 -0
  76. package/src/components/handbook-folder/ds-mobile-handbook-folder-mini.ts +175 -0
  77. package/src/components/handbook-folder/ds-mobile-handbook-folder.ts +533 -0
  78. package/src/components/handbook-folder/index.ts +4 -0
  79. package/src/components/header-content/ds-mobile-header-content.ts +222 -0
  80. package/src/components/header-content/index.ts +2 -0
  81. package/src/components/illustration/ds-mobile-illustration.ts +124 -0
  82. package/src/components/illustration/index.ts +2 -0
  83. package/src/components/index.ts +124 -0
  84. package/src/components/inline-photo/ds-mobile-inline-photo.ts +361 -0
  85. package/src/components/inline-photo/index.ts +1 -0
  86. package/src/components/inline-tabs/ds-mobile-inline-tabs.ts +132 -0
  87. package/src/components/inline-tabs/index.ts +2 -0
  88. package/src/components/interactive-list-item-booking/ds-mobile-interactive-list-item-booking.ts +350 -0
  89. package/src/components/interactive-list-item-booking/index.ts +1 -0
  90. package/src/components/interactive-list-item-inquiry/ds-mobile-interactive-list-item-inquiry.ts +321 -0
  91. package/src/components/interactive-list-item-inquiry/index.ts +2 -0
  92. package/src/components/interactive-list-item-message/ds-mobile-interactive-list-item-message.ts +237 -0
  93. package/src/components/interactive-list-item-message/index.ts +2 -0
  94. package/src/components/interactive-list-item-post/ds-mobile-interactive-list-item-post.ts +549 -0
  95. package/src/components/interactive-list-item-post/ds-mobile-post-pdf-attachment.ts +124 -0
  96. package/src/components/interactive-list-item-post/index.ts +13 -0
  97. package/src/components/lightbox/ds-mobile-lightbox-footer.ts +315 -0
  98. package/src/components/lightbox/ds-mobile-lightbox-header.ts +202 -0
  99. package/src/components/lightbox/ds-mobile-lightbox-image.ts +484 -0
  100. package/src/components/lightbox/ds-mobile-lightbox-pdf.css +377 -0
  101. package/src/components/lightbox/ds-mobile-lightbox-pdf.ts +374 -0
  102. package/src/components/lightbox/ds-mobile-lightbox.css +587 -0
  103. package/src/components/lightbox/ds-mobile-lightbox.service.ts +296 -0
  104. package/src/components/lightbox/ds-mobile-lightbox.ts +529 -0
  105. package/src/components/lightbox/index.ts +22 -0
  106. package/src/components/list-item/ds-mobile-list-item.ts +603 -0
  107. package/src/components/list-item/index.ts +2 -0
  108. package/src/components/list-item-static/ds-mobile-list-item-static.ts +133 -0
  109. package/src/components/list-item-static/index.ts +2 -0
  110. package/src/components/loader-overlay/ds-mobile-loader-overlay.css +49 -0
  111. package/src/components/loader-overlay/ds-mobile-loader-overlay.ts +77 -0
  112. package/src/components/loader-overlay/index.ts +1 -0
  113. package/src/components/logo/ds-logo.ts +95 -0
  114. package/src/components/logo/index.ts +2 -0
  115. package/src/components/message-bubble/ds-mobile-message-bubble.ts +633 -0
  116. package/src/components/message-bubble/index.ts +7 -0
  117. package/src/components/message-composer/ds-mobile-message-composer.ts +1146 -0
  118. package/src/components/message-composer/index.ts +7 -0
  119. package/src/components/modal/ds-mobile-modal.css +163 -0
  120. package/src/components/modal/ds-mobile-modal.service.ts +329 -0
  121. package/src/components/modal/index.ts +8 -0
  122. package/src/components/modal-base/ds-mobile-modal-base.css +378 -0
  123. package/src/components/modal-base/ds-mobile-modal-base.ts +261 -0
  124. package/src/components/modal-base/index.ts +2 -0
  125. package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.css +112 -0
  126. package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.service.ts +93 -0
  127. package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.ts +442 -0
  128. package/src/components/new-inquiry-modal/index.ts +4 -0
  129. package/src/components/offline-banner/ds-mobile-offline-banner.ts +135 -0
  130. package/src/components/offline-banner/index.ts +1 -0
  131. package/src/components/page-details/ds-mobile-page-details.css +83 -0
  132. package/src/components/page-details/ds-mobile-page-details.ts +282 -0
  133. package/src/components/page-details/index.ts +2 -0
  134. package/src/components/page-main/ds-mobile-page-main.css +68 -0
  135. package/src/components/page-main/ds-mobile-page-main.ts +421 -0
  136. package/src/components/page-main/index.ts +2 -0
  137. package/src/components/post-composer/ds-mobile-post-composer.ts +140 -0
  138. package/src/components/post-composer/index.ts +2 -0
  139. package/src/components/post-detail-modal/ds-mobile-post-detail-modal.css +390 -0
  140. package/src/components/post-detail-modal/ds-mobile-post-detail-modal.service.ts +108 -0
  141. package/src/components/post-detail-modal/ds-mobile-post-detail-modal.ts +722 -0
  142. package/src/components/post-detail-modal/index.ts +9 -0
  143. package/src/components/property-banner/ds-mobile-property-banner.ts +95 -0
  144. package/src/components/property-banner/index.ts +2 -0
  145. package/src/components/section/ds-mobile-section.ts +263 -0
  146. package/src/components/section/index.ts +2 -0
  147. package/src/components/shared/directives/index.ts +2 -0
  148. package/src/components/shared/directives/long-press.directive.ts +212 -0
  149. package/src/components/shared/index.ts +3 -0
  150. package/src/components/shared/mobile-modal-base.ts +457 -0
  151. package/src/components/shared/mobile-page-base.ts +204 -0
  152. package/src/components/swiper/ds-mobile-swiper-with-nav.ts +160 -0
  153. package/src/components/swiper/ds-mobile-swiper.ts +327 -0
  154. package/src/components/swiper/index.ts +3 -0
  155. package/src/components/system-message-banner/ds-mobile-system-message-banner.ts +129 -0
  156. package/src/components/system-message-banner/index.ts +2 -0
  157. package/src/components/tab-bar/ds-mobile-tab-bar.css +533 -0
  158. package/src/components/tab-bar/ds-mobile-tab-bar.ts +735 -0
  159. package/src/components/tab-bar/index.ts +2 -0
  160. package/src/components/tabs/ds-mobile-tabs.css +25 -0
  161. package/src/components/tabs/ds-mobile-tabs.ts +89 -0
  162. package/src/components/tabs/index.ts +2 -0
  163. package/src/components/text-input/ds-text-input.ts +287 -0
  164. package/src/components/text-input/index.ts +2 -0
  165. package/src/examples/booking.page.ts +434 -0
  166. package/src/examples/community.page.ts +776 -0
  167. package/src/examples/handbook.page.ts +324 -0
  168. package/src/examples/home.page.ts +347 -0
  169. package/src/examples/index.ts +12 -0
  170. package/src/examples/inquiries.example.ts +273 -0
  171. package/src/examples/inquiry-detail.example.css +189 -0
  172. package/src/examples/inquiry-detail.example.ts +415 -0
  173. package/src/examples/mobile-tabs-example.component.ts +208 -0
  174. package/src/examples/post-create.page.ts +311 -0
  175. package/src/examples/post-detail.page.ts +296 -0
  176. package/src/examples/sign-in.page.ts +291 -0
  177. package/src/examples/whitelabel-demo-modal.component.ts +1094 -0
  178. package/src/examples/whitelabel-demo-modal.service.ts +77 -0
  179. package/src/models/index.ts +7 -0
  180. package/src/models/post.model.ts +41 -0
  181. package/src/pages/community.page.ts +769 -0
  182. package/src/pages/handbook.page.ts +388 -0
  183. package/src/pages/home.page.ts +303 -0
  184. package/src/pages/index.ts +11 -0
  185. package/src/pages/inquiries.example.ts +273 -0
  186. package/src/pages/inquiry-detail.example.css +189 -0
  187. package/src/pages/inquiry-detail.example.ts +415 -0
  188. package/src/pages/mobile-tabs-example.component.ts +179 -0
  189. package/src/pages/post-create.page.ts +311 -0
  190. package/src/pages/post-detail.page.ts +296 -0
  191. package/src/pages/sign-in.page.ts +291 -0
  192. package/src/pages/whitelabel-demo-modal.component.ts +1094 -0
  193. package/src/pages/whitelabel-demo-modal.service.ts +77 -0
  194. package/src/public-api.ts +6 -0
  195. package/src/services/base-modal.service.ts +101 -0
  196. package/src/services/index.ts +11 -0
  197. package/src/services/posts.service.ts +542 -0
  198. package/src/services/tracking-permission.service.ts +88 -0
  199. package/src/services/user.service.ts +60 -0
  200. package/src/services/whitelabel.service.ts +675 -0
  201. package/{styles → src/styles}/ionic.css +25 -0
  202. package/tsconfig.lib.json +17 -0
  203. package/tsconfig.lib.prod.json +9 -0
  204. package/tsconfig.spec.json +13 -0
  205. package/fesm2022/propbinder-mobile-design.mjs +0 -26168
  206. package/fesm2022/propbinder-mobile-design.mjs.map +0 -1
  207. package/index.d.ts +0 -8169
  208. /package/{assets → src/assets}/fonts/Brockmann-Bold.otf +0 -0
  209. /package/{assets → src/assets}/fonts/Brockmann-BoldItalic.otf +0 -0
  210. /package/{assets → src/assets}/fonts/Brockmann-Medium.otf +0 -0
  211. /package/{assets → src/assets}/fonts/Brockmann-MediumItalic.otf +0 -0
  212. /package/{assets → src/assets}/fonts/Brockmann-Regular.otf +0 -0
  213. /package/{assets → src/assets}/fonts/Brockmann-RegularItalic.otf +0 -0
  214. /package/{assets → src/assets}/fonts/Brockmann-SemiBold.otf +0 -0
  215. /package/{assets → src/assets}/fonts/Brockmann-SemiBoldItalic.otf +0 -0
  216. /package/{assets → src/assets}/fonts/Brockmann_desktop_license.pdf +0 -0
  217. /package/{assets → src/assets}/fonts/brockmann-medium-webfont.woff2 +0 -0
  218. /package/{assets → src/assets}/fonts/brockmann-regular-webfont.woff2 +0 -0
  219. /package/{assets → src/assets}/fonts/brockmann-semibold-webfont.woff2 +0 -0
  220. /package/{styles → src/components/shared}/mobile-common.css +0 -0
  221. /package/{styles → src/components/shared}/mobile-page-base.css +0 -0
@@ -0,0 +1,415 @@
1
+ import { Component, signal, computed } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { DsIconComponent, DsBadgeComponent } from '@propbinder/design-system';
4
+ import { DsMobilePageDetailsComponent } from '../components/page-details';
5
+ import { DsMobileInteractiveListItemMessageComponent } from '../components/interactive-list-item-message';
6
+ import { DsMobileListItemComponent } from '../components/list-item/ds-mobile-list-item';
7
+ import { DsMobileLightboxService, type LightboxImage } from '../components/lightbox';
8
+ import { DsMobileChatModalService, type ChatModalData } from '../components/chat-modal';
9
+ import { DsAvatarWithBadgeComponent } from '../components/avatar-with-badge';
10
+ import { DsMobileCardInlineBannerComponent } from '../components/card-inline-banner';
11
+ import { DsMobileSectionComponent } from '../components/section';
12
+ import { DsMobileInlinePhotoComponent } from '../components/inline-photo';
13
+ import { UserService } from '../services/user.service';
14
+ import { type InlineTabItem } from '../components/inline-tabs';
15
+
16
+ interface MessageThread {
17
+ id: string;
18
+ senderName: string;
19
+ senderAvatar: string;
20
+ senderInitials: string;
21
+ message: string;
22
+ role: string;
23
+ timestamp: string;
24
+ unread: boolean;
25
+ }
26
+
27
+ @Component({
28
+ selector: 'app-mobile-inquiry-detail-page',
29
+ standalone: true,
30
+ imports: [
31
+ CommonModule,
32
+ DsIconComponent,
33
+ DsAvatarWithBadgeComponent,
34
+ DsMobilePageDetailsComponent,
35
+ DsMobileInteractiveListItemMessageComponent,
36
+ DsMobileListItemComponent,
37
+ DsBadgeComponent,
38
+ DsMobileCardInlineBannerComponent,
39
+ DsMobileSectionComponent,
40
+ DsMobileInlinePhotoComponent
41
+ ],
42
+ host: {
43
+ class: 'ion-page'
44
+ },
45
+ styleUrls: [
46
+ './inquiry-detail.example.css'
47
+ ],
48
+ template: `
49
+ <ds-mobile-page-details
50
+ [title]="inquiryTitle"
51
+ [tabs]="tabItems"
52
+ [activeTab]="activeTab()"
53
+ (tabChange)="setActiveTab($event)"
54
+ (back)="goBack()"
55
+ (refresh)="handleRefresh($event)">
56
+
57
+ <!-- Messages Tab Content -->
58
+ @if (activeTab() === 'messages') {
59
+ <ds-mobile-section contentGap="0">
60
+ @for (message of messageThreads; track message.id) {
61
+ <ds-mobile-interactive-list-item-message
62
+ [senderName]="message.senderName"
63
+ [senderRole]="message.role"
64
+ [timestamp]="message.timestamp"
65
+ [message]="message.message"
66
+ [avatarInitials]="message.senderInitials"
67
+ [unread]="message.unread"
68
+ (messageClick)="openMessage(message.id)">
69
+ </ds-mobile-interactive-list-item-message>
70
+ }
71
+ </ds-mobile-section>
72
+ }
73
+
74
+ <!-- Details Tab Content -->
75
+ @if (activeTab() === 'details') {
76
+ <!-- Unread Messages Banner -->
77
+ @if (hasUnreadMessages()) {
78
+ <ds-mobile-section>
79
+ <ds-mobile-card-inline-banner
80
+ [title]="'Du har ulæste beskeder'"
81
+ [unreadCount]="3"
82
+ [layout]="'compact'"
83
+ (bannerClick)="navigateToMessagesTab()">
84
+ </ds-mobile-card-inline-banner>
85
+ </ds-mobile-section>
86
+ }
87
+
88
+ <!-- All Details in One Section -->
89
+ <ds-mobile-section contentGap="0">
90
+ <!-- Assignee -->
91
+ <ds-mobile-list-item [leadingSize]="'32px'" [showDivider]="true">
92
+ <div content-leading>
93
+ <ds-avatar-with-badge
94
+ [size]="'sm'"
95
+ [type]="'initials'"
96
+ [initials]="'R'" />
97
+ </div>
98
+ <div content-main>
99
+ <div class="detail-label">Sagsbehandler</div>
100
+ <div class="detail-value">Ricki Meihlen</div>
101
+ </div>
102
+ </ds-mobile-list-item>
103
+
104
+ <!-- Technician -->
105
+ <ds-mobile-list-item [leadingSize]="'32px'" [showDivider]="true">
106
+ <div content-leading>
107
+ <ds-avatar-with-badge
108
+ [size]="'sm'"
109
+ [type]="'initials'"
110
+ [initials]="'M'" />
111
+ </div>
112
+ <div content-main>
113
+ <div class="detail-label">Tekniker</div>
114
+ <div class="detail-value">Martin Smith</div>
115
+ </div>
116
+ </ds-mobile-list-item>
117
+
118
+ <!-- Creation Date -->
119
+ <ds-mobile-list-item [leadingSize]="'32px'" [showDivider]="true" [align]="'center'">
120
+ <ds-icon content-leading name="remixTimeLine" size="20px" color="tertiary" />
121
+ <div content-main>
122
+ <div class="detail-value">22. feb 2025</div>
123
+ </div>
124
+ </ds-mobile-list-item>
125
+
126
+ <!-- Title -->
127
+ <ds-mobile-list-item [leadingSize]="'32px'" [showDivider]="true" [align]="'center'">
128
+ <ds-icon content-leading name="remixTextBlock" size="20px" color="tertiary" />
129
+ <div content-main>
130
+ <div class="detail-value">{{ inquiryTitle }}</div>
131
+ </div>
132
+ </ds-mobile-list-item>
133
+
134
+ <!-- Description -->
135
+ <ds-mobile-list-item [leadingSize]="'32px'" [showDivider]="true">
136
+ <ds-icon content-leading name="remixAlignLeft" size="20px" color="tertiary" />
137
+ <div content-main>
138
+ <div class="detail-value description-text">
139
+ I de sidste tre dage har vi oplevet vedvarende problemer med tørretumbleren i vores lejlighed. På trods af at vi følger betjeningsvejledningen, fejler maskinen konsekvent i at fuldføre sine tørrecyklusser.
140
+ </div>
141
+ <ds-badge content="Husholdningsapparater" size="sm"/>
142
+ </div>
143
+ </ds-mobile-list-item>
144
+
145
+ <!-- Photos -->
146
+ <ds-mobile-list-item [leadingSize]="'32px'" [showDivider]="false">
147
+ <ds-icon content-leading name="remixCameraLine" size="20px" color="tertiary" />
148
+ <div content-main>
149
+ <ds-mobile-inline-photo
150
+ [images]="photoUrls"
151
+ [useGrid]="false"
152
+ (photoClick)="openPhotoLightbox($event.index)" />
153
+ </div>
154
+ </ds-mobile-list-item>
155
+ </ds-mobile-section>
156
+ }
157
+ </ds-mobile-page-details>
158
+ `
159
+ })
160
+ export class MobileInquiryDetailPageComponent {
161
+ inquiryTitle = 'Tørretumbler virker ikke';
162
+ activeTab = signal<string>('details');
163
+
164
+ tabItems: InlineTabItem[] = [
165
+ { id: 'details', label: 'Detaljer' },
166
+ { id: 'messages', label: 'Beskeder', badge: 0 }
167
+ ];
168
+
169
+ messageThreads: MessageThread[] = [
170
+ {
171
+ id: '1',
172
+ senderName: 'Ove Hindborg',
173
+ senderAvatar: '',
174
+ senderInitials: 'OH',
175
+ message: 'Dejligt at høre! Jeg venter på din teknikerbesøg tidsplan.',
176
+ role: 'Sagsbehandler',
177
+ timestamp: '2t siden',
178
+ unread: true
179
+ },
180
+ {
181
+ id: '2',
182
+ senderName: 'Martin Smith',
183
+ senderAvatar: '',
184
+ senderInitials: 'MS',
185
+ message: 'Martin Smith har overtaget din henvendelse og vil kontakte dig snart.',
186
+ role: 'Tekniker',
187
+ timestamp: '',
188
+ unread: false
189
+ }
190
+ ];
191
+
192
+ unreadMessagesCount = computed(() => {
193
+ const count = this.messageThreads.length;
194
+ // Update badge in tab items
195
+ const messagesTab = this.tabItems.find(t => t.id === 'messages');
196
+ if (messagesTab) {
197
+ messagesTab.badge = count;
198
+ }
199
+ return count;
200
+ });
201
+
202
+ // Photos for lightbox
203
+ photos: LightboxImage[] = [
204
+ { type: 'image', src: '/Assets/Dummy-photos/handyman.jpg', alt: 'Handyman', title: 'Handyman' },
205
+ { type: 'image', src: '/Assets/Dummy-photos/balcony-view.jpg', alt: 'Balcony view', title: 'Balcony view' },
206
+ { type: 'image', src: '/Assets/Dummy-photos/staircase.jpg', alt: 'Staircase', title: 'Staircase' },
207
+ { type: 'image', src: '/Assets/Dummy-photos/yard.jpg', alt: 'Yard', title: 'Yard' },
208
+ { type: 'image', src: '/Assets/Dummy-photos/mailboxes.jpg', alt: 'Mailboxes', title: 'Mailboxes' }
209
+ ];
210
+
211
+ // Photo URLs for inline-photo component
212
+ get photoUrls(): string[] {
213
+ return this.photos.map(photo => photo.src);
214
+ }
215
+
216
+ constructor(
217
+ public userService: UserService,
218
+ private lightbox: DsMobileLightboxService,
219
+ private chatModal: DsMobileChatModalService
220
+ ) {
221
+ // Trigger initial badge update
222
+ this.unreadMessagesCount();
223
+ }
224
+
225
+ setActiveTab(tabId: string): void {
226
+ this.activeTab.set(tabId);
227
+ }
228
+
229
+ goBack(): void {
230
+ // Navigation is handled by ds-mobile-page-details component
231
+ // This is just for any custom logic if needed
232
+ }
233
+
234
+ handleRefresh(event: any): void {
235
+ console.log('Pull-to-refresh triggered');
236
+ setTimeout(() => {
237
+ console.log('Refresh complete');
238
+ event.target.complete();
239
+ }, 1000);
240
+ }
241
+
242
+ /**
243
+ * Check if there are unread messages
244
+ */
245
+ hasUnreadMessages(): boolean {
246
+ return this.messageThreads.some(m => m.unread);
247
+ }
248
+
249
+ /**
250
+ * Navigate to messages tab when banner is clicked
251
+ */
252
+ navigateToMessagesTab(): void {
253
+ this.setActiveTab('messages');
254
+ }
255
+
256
+ async openMessage(messageId: string): Promise<void> {
257
+ console.log('Opening message:', messageId);
258
+
259
+ // Find the message thread
260
+ const messageThread = this.messageThreads.find(m => m.id === messageId);
261
+ if (!messageThread) {
262
+ console.error('Message thread not found:', messageId);
263
+ return;
264
+ }
265
+
266
+ // Check if this is an empty state chat (no timestamp and system message)
267
+ const isEmptyChat = messageThread.message.includes('har overtaget din henvendelse') && !messageThread.timestamp;
268
+
269
+ // Prepare chat modal data
270
+ // In a real app, you would fetch the actual messages from your API
271
+ const chatData: ChatModalData = {
272
+ participant: {
273
+ id: messageId,
274
+ name: messageThread.senderName,
275
+ role: messageThread.role,
276
+ avatarInitials: messageThread.senderInitials,
277
+ avatarType: 'initials'
278
+ },
279
+ messages: isEmptyChat ? [] : [
280
+ // Day 1 - Yesterday morning (9:15 AM)
281
+ {
282
+ id: '1',
283
+ content: 'Godmorgen! Jeg ville lige følge op på din henvendelse vedrørende vedligeholdelsesplanen.',
284
+ senderId: messageId,
285
+ senderName: messageThread.senderName,
286
+ senderRole: messageThread.role,
287
+ timestamp: new Date(Date.now() - 86400000 - 32700000), // Yesterday 9:15 AM
288
+ isOwnMessage: false,
289
+ avatarInitials: messageThread.senderInitials,
290
+ avatarType: 'initials',
291
+ },
292
+ {
293
+ id: '2',
294
+ content: 'Vi har gennemgået din sag og identificeret den grundlæggende årsag til problemet.',
295
+ senderId: messageId,
296
+ senderName: messageThread.senderName,
297
+ senderRole: messageThread.role,
298
+ timestamp: new Date(Date.now() - 86400000 - 32520000), // Yesterday 9:18 AM (grouped with previous)
299
+ isOwnMessage: false,
300
+ avatarInitials: messageThread.senderInitials,
301
+ avatarType: 'initials',
302
+ },
303
+ {
304
+ id: '3',
305
+ content: 'Tak fordi du kiggede på det! Hvad fandt du?',
306
+ senderId: 'current-user',
307
+ senderName: 'You',
308
+ timestamp: new Date(Date.now() - 86400000 - 32100000), // Yesterday 9:25 AM (new group - 7 min gap)
309
+ isOwnMessage: true,
310
+ avatarInitials: this.userService.avatarInitials(),
311
+ avatarType: 'initials',
312
+ },
313
+ {
314
+ id: '4',
315
+ content: 'Kan du også sende mig vedligeholdelsesrapporten?',
316
+ senderId: 'current-user',
317
+ senderName: 'You',
318
+ timestamp: new Date(Date.now() - 86400000 - 31980000), // Yesterday 9:27 AM (grouped with previous)
319
+ isOwnMessage: true,
320
+ avatarInitials: this.userService.avatarInitials(),
321
+ avatarType: 'initials',
322
+ },
323
+
324
+ // Day 1 - Yesterday afternoon (2:30 PM - large time gap)
325
+ {
326
+ id: '5',
327
+ content: messageThread.message,
328
+ senderId: messageId,
329
+ senderName: messageThread.senderName,
330
+ senderRole: messageThread.role,
331
+ timestamp: new Date(Date.now() - 86400000 - 13800000), // Yesterday 2:30 PM (new group - 5 hour gap)
332
+ isOwnMessage: false,
333
+ avatarInitials: messageThread.senderInitials,
334
+ avatarType: 'initials',
335
+ },
336
+ {
337
+ id: '6',
338
+ content: 'Jeg har vedhæftet den detaljerede rapport på dit henvendelsesdashboard. Gennemgå den gerne, når du har et øjeblik.',
339
+ senderId: messageId,
340
+ senderName: messageThread.senderName,
341
+ senderRole: messageThread.role,
342
+ timestamp: new Date(Date.now() - 86400000 - 13620000), // Yesterday 2:33 PM (grouped with previous)
343
+ isOwnMessage: false,
344
+ avatarInitials: messageThread.senderInitials,
345
+ avatarType: 'initials',
346
+ },
347
+
348
+ // Day 2 - Today morning (10:45 AM - new day)
349
+ {
350
+ id: '7',
351
+ content: 'Perfekt! Jeg har gennemgået rapporten, og alt ser godt ud.',
352
+ senderId: 'current-user',
353
+ senderName: 'You',
354
+ timestamp: new Date(Date.now() - 7500000), // Today 10:45 AM (new group - new day)
355
+ isOwnMessage: true,
356
+ avatarInitials: this.userService.avatarInitials(),
357
+ avatarType: 'initials',
358
+ },
359
+ {
360
+ id: '8',
361
+ content: 'Hvornår kan vi planlægge vedligeholdelsesarbejdet?',
362
+ senderId: 'current-user',
363
+ senderName: 'You',
364
+ timestamp: new Date(Date.now() - 7320000), // Today 10:48 AM (grouped with previous)
365
+ isOwnMessage: true,
366
+ avatarInitials: this.userService.avatarInitials(),
367
+ avatarType: 'initials',
368
+ },
369
+
370
+ // Day 2 - Today (just now - 2 minutes ago)
371
+ {
372
+ id: '9',
373
+ content: 'Dejligt at høre! Jeg kan planlægge det til næste tirsdag kl. 09.00. Passer det dig?',
374
+ senderId: messageId,
375
+ senderName: messageThread.senderName,
376
+ senderRole: messageThread.role,
377
+ timestamp: new Date(Date.now() - 120000), // 2 minutes ago (new group - several hours gap)
378
+ isOwnMessage: false,
379
+ avatarInitials: messageThread.senderInitials,
380
+ avatarType: 'initials',
381
+ },
382
+ {
383
+ id: '10',
384
+ content: 'Jeg sender dig en kalenderinvitation med alle detaljerne.',
385
+ senderId: messageId,
386
+ senderName: messageThread.senderName,
387
+ senderRole: messageThread.role,
388
+ timestamp: new Date(Date.now() - 60000), // 1 minute ago (grouped with previous)
389
+ isOwnMessage: false,
390
+ avatarInitials: messageThread.senderInitials,
391
+ avatarType: 'initials',
392
+ }
393
+ ],
394
+ currentUserId: 'current-user',
395
+ currentUserInitials: this.userService.avatarInitials(),
396
+ currentUserAvatarType: 'initials',
397
+ autoFocus: false
398
+ };
399
+
400
+ // Open the chat modal
401
+ await this.chatModal.open(chatData);
402
+ }
403
+
404
+ async openPhotoLightbox(index: number): Promise<void> {
405
+ await this.lightbox.openImages({
406
+ images: this.photos,
407
+ initialIndex: index,
408
+ showControls: true,
409
+ enableSwipe: true,
410
+ enableZoom: true,
411
+ showInfo: false
412
+ });
413
+ }
414
+ }
415
+
@@ -0,0 +1,208 @@
1
+ import { Component, OnInit, computed, effect, inject } from '@angular/core';
2
+ import { Router } from '@angular/router';
3
+ import { CommonModule } from '@angular/common';
4
+ import { IonTabs } from '@ionic/angular/standalone';
5
+ import { UserService } from '../services/user.service';
6
+ import { DsMobileTabBarComponent, TabConfig } from '../components/tab-bar';
7
+ import { ActionResult, ActionGroup } from '../components/bottom-sheet';
8
+ import { WhitelabelDemoModalService } from './whitelabel-demo-modal.service';
9
+ import { TrackingPermissionService } from '../services/tracking-permission.service';
10
+
11
+ /**
12
+ * MobileTabsExampleComponent
13
+ *
14
+ * Example page using the TenantApp pattern:
15
+ * - Uses ion-tabs as wrapper (required for Angular routing)
16
+ * - Uses ds-mobile-tab-bar inside (not ds-mobile-tabs)
17
+ *
18
+ * This matches the pattern used in TenantApp for consistency.
19
+ */
20
+ @Component({
21
+ selector: 'app-mobile-tabs-example',
22
+ standalone: true,
23
+ imports: [CommonModule, IonTabs, DsMobileTabBarComponent],
24
+ styles: [
25
+ `
26
+ :host {
27
+ display: block;
28
+ height: 100dvh;
29
+ width: 100vw;
30
+ position: relative;
31
+ }
32
+ `,
33
+ ],
34
+ template: `
35
+ <ion-tabs class="ds-tabs-wrapper">
36
+ <!-- Tab bar with slot="top" on desktop, slot="bottom" on mobile -->
37
+ <!-- Ionic automatically creates ion-tab elements from child routes -->
38
+ <ds-mobile-tab-bar
39
+ [tabs]="tabs"
40
+ [avatarInitials]="userService.avatarInitials()"
41
+ [avatarType]="userService.avatarType()"
42
+ [profileMenuItems]="profileMenuItems"
43
+ (profileActionSelected)="handleProfileAction($event)"
44
+ >
45
+ </ds-mobile-tab-bar>
46
+ </ion-tabs>
47
+ `,
48
+ })
49
+ export class MobileTabsExampleComponent implements OnInit {
50
+ private whitelabelDemoModal = inject(WhitelabelDemoModalService);
51
+ private trackingPermissionService = inject(TrackingPermissionService);
52
+ private trackedProfileMenuItems = computed<ActionGroup[]>(() => {
53
+ const accountActions = [
54
+ {
55
+ action: 'profile',
56
+ title: 'Min profil',
57
+ icon: 'remixUser3Line',
58
+ destructive: false,
59
+ },
60
+ {
61
+ action: 'settings',
62
+ title: 'Indstillinger',
63
+ icon: 'remixSettings3Line',
64
+ destructive: false,
65
+ },
66
+ {
67
+ action: 'appearance',
68
+ title: 'Udseende',
69
+ icon: 'remixPaletteLine',
70
+ destructive: false,
71
+ },
72
+ ];
73
+
74
+ if (this.trackingPermissionService.shouldShowSettingsReminder()) {
75
+ accountActions.push({
76
+ action: 'tracking-settings',
77
+ title: 'Sporingsindstillinger',
78
+ icon: 'remixSettings3Line',
79
+ destructive: false,
80
+ });
81
+ }
82
+
83
+ return [
84
+ {
85
+ actions: accountActions,
86
+ },
87
+ {
88
+ actions: [
89
+ {
90
+ action: 'language',
91
+ title: 'Sprog',
92
+ subtitle: 'Dansk',
93
+ flagIcon: '/Assets/country-flags/denmark.svg',
94
+ icon: 'remixGlobalLine',
95
+ destructive: false,
96
+ showChevron: true,
97
+ },
98
+ ],
99
+ },
100
+ {
101
+ actions: [
102
+ {
103
+ action: 'logout',
104
+ title: 'Log ud',
105
+ icon: 'remixLogoutBoxLine',
106
+ destructive: true,
107
+ },
108
+ ],
109
+ },
110
+ ];
111
+ });
112
+
113
+ constructor(
114
+ public userService: UserService,
115
+ private router: Router,
116
+ ) {
117
+ console.log('MobileTabsExampleComponent constructor');
118
+ effect(() => {
119
+ this.userService.setProfileMenuItems(this.trackedProfileMenuItems());
120
+ });
121
+ }
122
+
123
+ ngOnInit() {
124
+ console.log('MobileTabsExampleComponent ngOnInit');
125
+ // Configure user avatar globally - this is now the single source of truth
126
+ this.userService.setAvatarInitials('LM');
127
+ this.userService.setAvatarType('initials');
128
+
129
+ // Initial status refresh ensures menu reflects past ATT choice.
130
+ void this.trackingPermissionService.refreshTrackingStatus();
131
+ }
132
+
133
+ tabs: TabConfig[] = [
134
+ {
135
+ id: 'home',
136
+ label: 'Hjem',
137
+ route: 'home',
138
+ icon: 'remixHomeSmile2Line',
139
+ iconActive: 'remixHomeSmile2Fill',
140
+ },
141
+ {
142
+ id: 'inquiries',
143
+ label: 'Henvendelser',
144
+ route: 'inquiries',
145
+ icon: 'remixFileList3Line',
146
+ iconActive: 'remixFileList3Fill',
147
+ },
148
+ {
149
+ id: 'announcements',
150
+ label: 'Fællesskab',
151
+ route: 'announcements',
152
+ icon: 'remixCommunityLine',
153
+ iconActive: 'remixCommunityFill',
154
+ },
155
+ {
156
+ id: 'booking',
157
+ label: 'Booking',
158
+ route: 'booking',
159
+ icon: 'remixCalendarCheckLine',
160
+ iconActive: 'remixCalendarCheckFill',
161
+ },
162
+ {
163
+ id: 'handbook',
164
+ label: 'Håndbog',
165
+ route: 'handbook',
166
+ icon: 'remixBook2Line',
167
+ iconActive: 'remixBook2Fill',
168
+ },
169
+ ];
170
+
171
+ /**
172
+ * Profile menu items configuration.
173
+ * Define once here - this is set globally in UserService in ngOnInit(),
174
+ * so it will be used by both ds-mobile-tab-bar and ds-mobile-page-main components
175
+ * throughout the entire application.
176
+ */
177
+ get profileMenuItems(): ActionGroup[] {
178
+ return this.trackedProfileMenuItems();
179
+ }
180
+
181
+ /**
182
+ * Handle profile menu action selection.
183
+ * The tab bar component handles the UI (opening/closing menu),
184
+ * this method handles the business logic.
185
+ *
186
+ * NOTE: Desktop only - called directly from tab bar.
187
+ * Mobile actions are handled globally in AppComponent via UserService.
188
+ */
189
+ handleProfileAction(result: ActionResult): void {
190
+ console.log('Profile action selected (desktop tab bar):', result.action);
191
+
192
+ // Handle appearance action here (opens modal)
193
+ if (result.action === 'appearance') {
194
+ console.log('Opening whitelabel demo...');
195
+ // Small delay to ensure bottom sheet is fully dismissed
196
+ setTimeout(async () => {
197
+ try {
198
+ await this.whitelabelDemoModal.open();
199
+ } catch (error) {
200
+ console.error('Failed to open whitelabel demo modal:', error);
201
+ }
202
+ }, 100);
203
+ }
204
+
205
+ // Notify globally so AppComponent can handle navigation
206
+ this.userService.notifyProfileAction(result);
207
+ }
208
+ }