@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,656 @@
1
+ import { Component, signal, Input, ViewChild, ElementRef, OnInit, AfterViewInit, CUSTOM_ELEMENTS_SCHEMA, inject } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { FormsModule } from '@angular/forms';
4
+ import { ModalController } from '@ionic/angular/standalone';
5
+ import { Capacitor } from '@capacitor/core';
6
+ import { FilePicker } from '@capawesome/capacitor-file-picker';
7
+ import { DsButtonComponent, DsTextareaComponent, DsIconComponent, DsIconButtonComponent } from '@propbinder/design-system';
8
+ import { DsMobileModalBaseComponent } from '../modal-base/ds-mobile-modal-base';
9
+ import { DsMobileSectionComponent } from '../section';
10
+ import { DsMobileListItemComponent } from '../list-item';
11
+ import { DsMobileBottomSheetService } from '../bottom-sheet';
12
+ import { DsMobileAttachmentPreviewComponent, type AttachmentData, type AttachmentFileType } from '../attachment-preview';
13
+
14
+ /**
15
+ * New facility form data
16
+ */
17
+ export interface NewFacilityData {
18
+ title: string;
19
+ description: string;
20
+ whoCanBook: string;
21
+ whenCanBook: string;
22
+ price: string;
23
+ accessRequirements: string;
24
+ attachments: AttachmentData[];
25
+ }
26
+
27
+ /**
28
+ * DsMobileFacilityCreationModalComponent
29
+ *
30
+ * Modal component for creating new facilities.
31
+ * Uses ds-mobile-modal-base for consistent layout and behavior.
32
+ *
33
+ * Features:
34
+ * - Title and description fields
35
+ * - Interactive list items for facility options
36
+ * - Bottom sheet pickers for selections
37
+ * - Photo upload with preview
38
+ * - Form validation
39
+ *
40
+ * This component is typically not used directly - use DsMobileFacilityCreationModalService instead.
41
+ */
42
+ @Component({
43
+ selector: 'ds-mobile-facility-creation-modal',
44
+ standalone: true,
45
+ imports: [
46
+ CommonModule,
47
+ FormsModule,
48
+ DsButtonComponent,
49
+ DsTextareaComponent,
50
+ DsIconComponent,
51
+ DsIconButtonComponent,
52
+ DsMobileModalBaseComponent,
53
+ DsMobileSectionComponent,
54
+ DsMobileListItemComponent,
55
+ DsMobileAttachmentPreviewComponent,
56
+ ],
57
+ styleUrls: ['../shared/mobile-common.css', './ds-mobile-facility-creation-modal.css'],
58
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
59
+ template: `
60
+ <ds-mobile-modal-base
61
+ [loading]="loading"
62
+ [error]="error"
63
+ [showHeader]="false"
64
+ [hasFixedBottom]="true"
65
+ [enableKeyboardHandling]="true"
66
+ [keyboardContentBehavior]="'overlay'"
67
+ closeButtonLabel="Close"
68
+ >
69
+ <!-- Form Content -->
70
+ <ds-mobile-section class="form-section">
71
+ <!-- Title Field (Large Ghost Textarea) -->
72
+ <ds-textarea
73
+ #titleInput
74
+ [(ngModel)]="title"
75
+ [ghost]="true"
76
+ [required]="true"
77
+ [rows]="1"
78
+ [placeholder]="titlePlaceholder"
79
+ class="inquiry-title-input ghost-input-clean"
80
+ (valueChange)="handleTitleChange($event)"
81
+ />
82
+
83
+ <!-- Description Field (Ghost Textarea) -->
84
+ <ds-textarea
85
+ #descriptionInput
86
+ [(ngModel)]="description"
87
+ [ghost]="true"
88
+ [rows]="1"
89
+ [placeholder]="descriptionPlaceholder"
90
+ class="inquiry-description-input ghost-input-clean"
91
+ (valueChange)="handleDescriptionChange($event)"
92
+ />
93
+ </ds-mobile-section>
94
+
95
+ <!-- List Items Section -->
96
+ <ds-mobile-section [contentGap]="'0'">
97
+ <!-- Who can book - hidden until Propbinder API supports this field -->
98
+ <!-- <ds-mobile-list-item
99
+ [leadingSize]="'32px'"
100
+ [showDivider]="true"
101
+ [interactive]="true"
102
+ [showDesktopMoreButton]="false"
103
+ [align]="'center'"
104
+ (itemClick)="openWhoSheet()">
105
+ <ds-icon content-leading name="remixGroupLine" size="20px" color="tertiary" />
106
+ <div content-main>
107
+ <div class="detail-label">Hvem kan booke</div>
108
+ <div class="detail-value">{{ whoCanBook() }}</div>
109
+ </div>
110
+ <ds-icon content-trailing name="remixArrowRightSLine" size="20px" color="tertiary" />
111
+ </ds-mobile-list-item> -->
112
+
113
+ <!-- When can it be booked -->
114
+ <ds-mobile-list-item
115
+ [leadingSize]="'32px'"
116
+ [showDivider]="true"
117
+ [interactive]="true"
118
+ [showDesktopMoreButton]="false"
119
+ [align]="'center'"
120
+ (itemClick)="openWhenSheet()">
121
+ <ds-icon content-leading name="remixCalendarLine" size="20px" color="tertiary" />
122
+ <div content-main>
123
+ <div class="detail-label">Hvornår kan det bookes</div>
124
+ <div class="detail-value">{{ whenCanBook() }}</div>
125
+ </div>
126
+ <ds-icon content-trailing name="remixArrowRightSLine" size="20px" color="tertiary" />
127
+ </ds-mobile-list-item>
128
+
129
+ <!-- Price -->
130
+ <ds-mobile-list-item
131
+ [leadingSize]="'32px'"
132
+ [showDivider]="false"
133
+ [interactive]="true"
134
+ [showDesktopMoreButton]="false"
135
+ [align]="'center'"
136
+ (itemClick)="openPriceSheet()">
137
+ <ds-icon content-leading name="remixPriceTag3Line" size="20px" color="tertiary" />
138
+ <div content-main>
139
+ <div class="detail-label">Pris</div>
140
+ <div class="detail-value">{{ price() }}</div>
141
+ </div>
142
+ <ds-icon content-trailing name="remixArrowRightSLine" size="20px" color="tertiary" />
143
+ </ds-mobile-list-item>
144
+
145
+ <!-- Access requirements - hidden until Propbinder API supports this field -->
146
+ <!-- <ds-mobile-list-item
147
+ [leadingSize]="'32px'"
148
+ [showDivider]="false"
149
+ [interactive]="true"
150
+ [showDesktopMoreButton]="false"
151
+ [align]="'center'"
152
+ (itemClick)="openAccessSheet()">
153
+ <ds-icon content-leading name="remixKeyLine" size="20px" color="tertiary" />
154
+ <div content-main>
155
+ <div class="detail-label">Adgangskrav</div>
156
+ <div class="detail-value">{{ accessRequirements() }}</div>
157
+ </div>
158
+ <ds-icon content-trailing name="remixArrowRightSLine" size="20px" color="tertiary" />
159
+ </ds-mobile-list-item> -->
160
+ </ds-mobile-section>
161
+
162
+ <!-- Fixed Bottom Container (Slides with keyboard) -->
163
+ <div fixed-bottom class="fixed-bottom-container">
164
+ <!-- Attachment Previews (if any) -->
165
+ @if (attachments().length > 0) {
166
+ <div class="attachment-previews-section">
167
+ <div class="image-previews">
168
+ @for (attachment of attachments(); track attachment.id) {
169
+ <ds-mobile-attachment-preview [attachment]="attachment" (remove)="removeAttachment($event)" />
170
+ }
171
+ </div>
172
+ </div>
173
+ }
174
+
175
+ <!-- Submit Actions Container -->
176
+ <div class="submit-container">
177
+ <div class="submit-content">
178
+ <!-- Upload Actions (Left) -->
179
+ <div class="upload-actions">
180
+ <ds-icon-button icon="remixImageLine" variant="secondary" size="md" (clicked)="addPhoto()" [disabled]="attachments().length >= 6" aria-label="Add photo">
181
+ </ds-icon-button>
182
+ <ds-icon-button
183
+ icon="remixAttachmentLine"
184
+ variant="secondary"
185
+ size="md"
186
+ (clicked)="handleAddAttachment()"
187
+ [disabled]="attachments().length >= 6"
188
+ aria-label="Add attachment"
189
+ >
190
+ </ds-icon-button>
191
+
192
+ <!-- Hidden file input for file selection -->
193
+ <input #fileInput type="file" accept="*/*" multiple (change)="handleFileSelect($event)" style="display: none;" aria-hidden="true" />
194
+ </div>
195
+
196
+ <!-- Submit Button (Right) -->
197
+ <ds-button class="submit-action-button" variant="primary" size="sm" [disabled]="!isFormValid() || isSubmitting()" (clicked)="handleSubmit()">
198
+ {{ submitButtonLabel }}
199
+ </ds-button>
200
+ </div>
201
+ </div>
202
+ </div>
203
+ </ds-mobile-modal-base>
204
+ `
205
+ })
206
+ export class DsMobileFacilityCreationModalComponent implements OnInit, AfterViewInit {
207
+ private modalController = inject(ModalController);
208
+ private bottomSheetService = inject(DsMobileBottomSheetService);
209
+
210
+ @ViewChild('titleInput', { read: ElementRef }) titleInputRef?: ElementRef<HTMLElement>;
211
+ @ViewChild('descriptionInput', { read: ElementRef }) descriptionInputRef?: ElementRef<HTMLElement>;
212
+ @ViewChild('titleInput') titleInput?: DsTextareaComponent;
213
+ @ViewChild('fileInput') fileInput?: ElementRef<HTMLInputElement>;
214
+
215
+ /**
216
+ * Loading state for the modal
217
+ */
218
+ @Input() loading: boolean = false;
219
+
220
+ /**
221
+ * Error message to display
222
+ */
223
+ @Input() error?: string;
224
+
225
+ /**
226
+ * Callback function when form is submitted
227
+ */
228
+ @Input() onSubmit?: (data: NewFacilityData) => void | Promise<void>;
229
+
230
+ /**
231
+ * Placeholder for the title field
232
+ */
233
+ @Input() titlePlaceholder: string = 'Festlokale på tal';
234
+
235
+ /**
236
+ * Placeholder for the description field
237
+ */
238
+ @Input() descriptionPlaceholder: string = 'Beskriv din booking så detaljeret som muligt...';
239
+
240
+ /**
241
+ * Label for the submit button
242
+ */
243
+ @Input() submitButtonLabel: string = 'Opret';
244
+
245
+ /**
246
+ * Form title field
247
+ */
248
+ title = '';
249
+
250
+ /**
251
+ * Form description field
252
+ */
253
+ description = '';
254
+
255
+ /**
256
+ * Attachments array
257
+ */
258
+ attachments = signal<AttachmentData[]>([]);
259
+
260
+ /**
261
+ * Who can book signal
262
+ */
263
+ whoCanBook = signal<string>('Alle');
264
+
265
+ /**
266
+ * When can it be booked signal
267
+ */
268
+ whenCanBook = signal<string>('I weekenden, 09:00 til 17:30');
269
+
270
+ /**
271
+ * Price signal
272
+ */
273
+ price = signal<string>('Gratis');
274
+
275
+ /**
276
+ * Access requirements signal
277
+ */
278
+ accessRequirements = signal<string>('Ingen adgangskrav');
279
+
280
+ /**
281
+ * Form validation state
282
+ */
283
+ isFormValid = signal<boolean>(false);
284
+
285
+ /**
286
+ * Submitting state
287
+ */
288
+ isSubmitting = signal<boolean>(false);
289
+
290
+ ngOnInit(): void {
291
+ console.log('[FacilityCreationModal] Component initialized');
292
+ }
293
+
294
+ ngAfterViewInit(): void {
295
+ // Setup auto-resize for title textarea
296
+ setTimeout(() => {
297
+ this.autoResizeTitleTextarea();
298
+ this.autoResizeDescriptionTextarea();
299
+
300
+ // Focus the title textarea after view initialization to trigger keyboard on iOS
301
+ if (this.titleInputRef) {
302
+ const textareaElement = this.titleInputRef.nativeElement.querySelector('textarea');
303
+ if (textareaElement) {
304
+ textareaElement.focus();
305
+ }
306
+ }
307
+ }, 300);
308
+ }
309
+
310
+ /**
311
+ * Auto-resize the title textarea based on content
312
+ */
313
+ private autoResizeTitleTextarea(): void {
314
+ if (!this.titleInputRef) return;
315
+
316
+ // Access the native textarea element
317
+ const textareaElement = this.titleInputRef.nativeElement.querySelector('textarea');
318
+ if (textareaElement) {
319
+ textareaElement.style.height = 'auto';
320
+ textareaElement.style.height = textareaElement.scrollHeight + 'px';
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Auto-resize the description textarea based on content
326
+ */
327
+ private autoResizeDescriptionTextarea(): void {
328
+ if (!this.descriptionInputRef) return;
329
+
330
+ const textareaElement = this.descriptionInputRef.nativeElement.querySelector('textarea');
331
+ if (textareaElement) {
332
+ textareaElement.style.height = 'auto';
333
+ textareaElement.style.height = textareaElement.scrollHeight + 'px';
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Handle title change with auto-resize
339
+ */
340
+ handleTitleChange(value: string): void {
341
+ this.validateForm();
342
+ this.autoResizeTitleTextarea();
343
+ }
344
+
345
+ /**
346
+ * Handle description change with auto-resize
347
+ */
348
+ handleDescriptionChange(value: string): void {
349
+ this.validateForm();
350
+ this.autoResizeDescriptionTextarea();
351
+ }
352
+
353
+ /**
354
+ * Validate form fields
355
+ */
356
+ validateForm(): void {
357
+ const isValid = this.title.trim().length > 0 && this.description.trim().length > 0;
358
+ this.isFormValid.set(isValid);
359
+ }
360
+
361
+ /**
362
+ * Open who can book sheet
363
+ */
364
+ async openWhoSheet(): Promise<void> {
365
+ console.log('[FacilityCreationModal] Opening who can book sheet');
366
+ const { DsMobileWhoCanBookSheetComponent } = await import('./sheets/ds-mobile-who-can-book-sheet');
367
+
368
+ const modal = await this.bottomSheetService.create({
369
+ component: DsMobileWhoCanBookSheetComponent,
370
+ componentProps: {},
371
+ breakpoints: [0, 1],
372
+ initialBreakpoint: 1,
373
+ handle: true,
374
+ cssClass: ['ds-bottom-sheet', 'auto-height']
375
+ });
376
+
377
+ const result = await modal.onWillDismiss();
378
+ if (result?.data?.value) {
379
+ // value is now an array of selected community IDs
380
+ const selectedIds = result.data.value as string[];
381
+
382
+ // If all non-"alle" options are selected, just display "Alle"
383
+ const allCommunities = ['faelleskab-a', 'faelleskab-b', 'faelleskab-c'];
384
+ const allCommunitiesSelected = allCommunities.every(id => selectedIds.includes(id));
385
+
386
+ if (allCommunitiesSelected || selectedIds.includes('alle')) {
387
+ this.whoCanBook.set('Alle');
388
+ } else {
389
+ // Otherwise, show individual labels
390
+ const labels = selectedIds
391
+ .filter(id => id !== 'alle') // Exclude "alle" from individual display
392
+ .map((id: string) => {
393
+ // Convert IDs to display labels
394
+ if (id === 'faelleskab-a') return 'Fælleskab A';
395
+ if (id === 'faelleskab-b') return 'Fælleskab B';
396
+ if (id === 'faelleskab-c') return 'Fælleskab C';
397
+ return id;
398
+ });
399
+ this.whoCanBook.set(labels.join(', '));
400
+ }
401
+ }
402
+ }
403
+
404
+ /**
405
+ * Open when can it be booked sheet
406
+ */
407
+ async openWhenSheet(): Promise<void> {
408
+ console.log('[FacilityCreationModal] Opening when sheet');
409
+ const { DsMobileWhenCanBookSheetComponent } = await import('./sheets/ds-mobile-when-can-book-sheet');
410
+
411
+ const modal = await this.bottomSheetService.create({
412
+ component: DsMobileWhenCanBookSheetComponent,
413
+ componentProps: {},
414
+ breakpoints: [0, 1],
415
+ initialBreakpoint: 1,
416
+ handle: true,
417
+ cssClass: ['ds-bottom-sheet', 'auto-height']
418
+ });
419
+
420
+ const result = await modal.onWillDismiss();
421
+ if (result?.data?.value) {
422
+ // value is now an object with days, timeRange, duration
423
+ const { days, timeRange, duration } = result.data.value;
424
+ const dayStr = days.join(', ');
425
+ const timeStr = `${timeRange.start} til ${timeRange.end}`;
426
+ this.whenCanBook.set(`${dayStr}, ${timeStr}, ${duration}`);
427
+ }
428
+ }
429
+
430
+ /**
431
+ * Open price sheet
432
+ */
433
+ async openPriceSheet(): Promise<void> {
434
+ console.log('[FacilityCreationModal] Opening price sheet');
435
+ const { DsMobilePriceSheetComponent } = await import('./sheets/ds-mobile-price-sheet');
436
+
437
+ const modal = await this.bottomSheetService.create({
438
+ component: DsMobilePriceSheetComponent,
439
+ componentProps: {},
440
+ breakpoints: [0, 1],
441
+ initialBreakpoint: 1,
442
+ handle: true,
443
+ cssClass: ['ds-bottom-sheet', 'auto-height']
444
+ });
445
+
446
+ const result = await modal.onWillDismiss();
447
+ if (result?.data?.value) {
448
+ this.price.set(result.data.value);
449
+ }
450
+ }
451
+
452
+ /**
453
+ * Open access requirements sheet
454
+ */
455
+ async openAccessSheet(): Promise<void> {
456
+ console.log('[FacilityCreationModal] Opening access sheet');
457
+ const { DsMobileAccessSheetComponent } = await import('./sheets/ds-mobile-access-sheet');
458
+
459
+ const modal = await this.bottomSheetService.create({
460
+ component: DsMobileAccessSheetComponent,
461
+ componentProps: {},
462
+ breakpoints: [0, 1],
463
+ initialBreakpoint: 1,
464
+ handle: true,
465
+ cssClass: ['ds-bottom-sheet', 'auto-height']
466
+ });
467
+
468
+ const result = await modal.onWillDismiss();
469
+ if (result?.data?.value !== undefined) {
470
+ // value is now an array of selected restriction IDs
471
+ const selectedIds = result.data.value as string[];
472
+
473
+ // If no restrictions are selected, show "Ingen adgangskrav"
474
+ if (selectedIds.length === 0) {
475
+ this.accessRequirements.set('Ingen adgangskrav');
476
+ } else {
477
+ const labels = selectedIds.map((id: string) => {
478
+ // Convert IDs to display labels
479
+ if (id === 'digital-adgangskode') return 'Digital adgangskode';
480
+ if (id === 'depositum') return 'Depositum';
481
+ if (id === 'nøglekort') return 'Nøglekort';
482
+ return id;
483
+ });
484
+ this.accessRequirements.set(labels.join(', '));
485
+ }
486
+ }
487
+ }
488
+
489
+ /**
490
+ * Add a new photo from camera/library
491
+ */
492
+ async addPhoto(): Promise<void> {
493
+ if (this.attachments().length >= 6) {
494
+ return;
495
+ }
496
+
497
+ try {
498
+ const result = await FilePicker.pickImages({
499
+ limit: 1,
500
+ });
501
+
502
+ const image = result.files?.[0];
503
+ if (image) {
504
+ const newAttachment: AttachmentData = {
505
+ id: `photo-${Date.now()}`,
506
+ src: image.path ? Capacitor.convertFileSrc(image.path) : (image.blob ? URL.createObjectURL(image.blob) : ''),
507
+ type: 'image',
508
+ name: image.name || `Photo ${this.attachments().length + 1}`,
509
+ size: this.formatFileSize(image.size ?? 0),
510
+ };
511
+
512
+ this.attachments.update((attachments) => [...attachments, newAttachment]);
513
+ }
514
+ } catch (error) {
515
+ console.error('[FacilityCreationModal] Error adding photo:', error);
516
+ // User cancelled or error occurred - just ignore
517
+ }
518
+ }
519
+
520
+ /**
521
+ * Remove an attachment
522
+ */
523
+ removeAttachment(attachmentId: string): void {
524
+ this.attachments.update((attachments) => attachments.filter((a) => a.id !== attachmentId));
525
+ }
526
+
527
+ /**
528
+ * Handle attachment button click
529
+ */
530
+ handleAddAttachment(): void {
531
+ if (this.attachments().length >= 6) {
532
+ return;
533
+ }
534
+
535
+ // Trigger the hidden file input
536
+ if (this.fileInput) {
537
+ this.fileInput.nativeElement.click();
538
+ }
539
+ }
540
+
541
+ /**
542
+ * Detect file type from file name or mime type
543
+ */
544
+ private detectFileType(file: File): AttachmentFileType {
545
+ const fileName = file.name.toLowerCase();
546
+ const mimeType = file.type.toLowerCase();
547
+
548
+ // Check if it's an image
549
+ if (mimeType.startsWith('image/')) {
550
+ return 'image';
551
+ }
552
+
553
+ // Check file extension
554
+ if (fileName.endsWith('.pdf')) {
555
+ return 'pdf';
556
+ } else if (fileName.endsWith('.doc')) {
557
+ return 'doc';
558
+ } else if (fileName.endsWith('.docx')) {
559
+ return 'docx';
560
+ } else if (fileName.endsWith('.xls')) {
561
+ return 'xls';
562
+ } else if (fileName.endsWith('.xlsx')) {
563
+ return 'xlsx';
564
+ }
565
+
566
+ return 'other';
567
+ }
568
+
569
+ /**
570
+ * Format file size for display
571
+ */
572
+ private formatFileSize(bytes: number): string {
573
+ if (bytes === 0) return '0 B';
574
+ const k = 1024;
575
+ const sizes = ['B', 'KB', 'MB', 'GB'];
576
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
577
+ return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
578
+ }
579
+
580
+ /**
581
+ * Handle file selection from file input
582
+ */
583
+ handleFileSelect(event: Event): void {
584
+ const input = event.target as HTMLInputElement;
585
+ const files = input.files;
586
+
587
+ if (!files || files.length === 0) {
588
+ return;
589
+ }
590
+
591
+ // Process each selected file (up to the limit)
592
+ const remainingSlots = 6 - this.attachments().length;
593
+ const filesToProcess = Array.from(files).slice(0, remainingSlots);
594
+
595
+ filesToProcess.forEach((file) => {
596
+ const fileType = this.detectFileType(file);
597
+
598
+ // Create a data URL for preview
599
+ const reader = new FileReader();
600
+ reader.onload = (e) => {
601
+ const result = e.target?.result as string;
602
+ if (result) {
603
+ const newAttachment: AttachmentData = {
604
+ id: `file-${Date.now()}-${Math.random()}`,
605
+ src: result,
606
+ type: fileType,
607
+ name: file.name,
608
+ size: this.formatFileSize(file.size),
609
+ };
610
+ this.attachments.update((attachments) => [...attachments, newAttachment]);
611
+ }
612
+ };
613
+ reader.readAsDataURL(file);
614
+ });
615
+
616
+ // Reset the input so the same file can be selected again
617
+ input.value = '';
618
+ }
619
+
620
+ /**
621
+ * Handle form submission
622
+ */
623
+ async handleSubmit(): Promise<void> {
624
+ if (!this.isFormValid() || this.isSubmitting()) {
625
+ return;
626
+ }
627
+
628
+ this.isSubmitting.set(true);
629
+
630
+ try {
631
+ const facilityData: NewFacilityData = {
632
+ title: this.title.trim(),
633
+ description: this.description.trim(),
634
+ whoCanBook: this.whoCanBook(),
635
+ whenCanBook: this.whenCanBook(),
636
+ price: this.price(),
637
+ accessRequirements: this.accessRequirements(),
638
+ attachments: this.attachments(),
639
+ };
640
+
641
+ console.log('[FacilityCreationModal] Submitting facility:', facilityData);
642
+
643
+ if (this.onSubmit) {
644
+ await this.onSubmit(facilityData);
645
+ }
646
+
647
+ // Success - close the modal
648
+ this.modalController.dismiss();
649
+ } catch (error) {
650
+ console.error('[FacilityCreationModal] Error submitting facility:', error);
651
+ this.error = 'Failed to create facility. Please try again.';
652
+ } finally {
653
+ this.isSubmitting.set(false);
654
+ }
655
+ }
656
+ }
@@ -0,0 +1,9 @@
1
+ export { DsMobileFacilityCreationModalComponent } from './ds-mobile-facility-creation-modal';
2
+ export type { NewFacilityData } from './ds-mobile-facility-creation-modal';
3
+ export { DsMobileFacilityCreationModalService } from './ds-mobile-facility-creation-modal.service';
4
+ export type { FacilityCreationModalOptions } from './ds-mobile-facility-creation-modal.service';
5
+ export { DsMobileFacilityCreationConfirmationWrapperComponent } from './ds-mobile-facility-creation-confirmation-wrapper';
6
+ export { DsMobileWhoCanBookSheetComponent } from './sheets/ds-mobile-who-can-book-sheet';
7
+ export { DsMobileWhenCanBookSheetComponent } from './sheets/ds-mobile-when-can-book-sheet';
8
+ export { DsMobilePriceSheetComponent } from './sheets/ds-mobile-price-sheet';
9
+ export { DsMobileAccessSheetComponent } from './sheets/ds-mobile-access-sheet';