@conduction/nextcloud-vue 2.79.0 → 2.81.0

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 (390) hide show
  1. package/dist/esm/components/CnActionButtons/CnActionButtons.vue2.js.map +1 -1
  2. package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js +3 -1
  3. package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js.map +1 -1
  4. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +32 -3
  5. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  6. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +13 -11
  7. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  8. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +80 -4
  9. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  10. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +95 -91
  11. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  12. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +179 -13
  13. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
  14. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +52 -16
  15. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
  16. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +96 -4
  17. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  18. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +158 -110
  19. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  20. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js +10 -0
  21. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js.map +1 -0
  22. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js +252 -0
  23. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js.map +1 -0
  24. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js +140 -0
  25. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js.map +1 -0
  26. package/dist/esm/components/CnChipsFieldWidget/index.js +26 -0
  27. package/dist/esm/components/CnChipsFieldWidget/index.js.map +1 -0
  28. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js +10 -0
  29. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js.map +1 -0
  30. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js +354 -0
  31. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js.map +1 -0
  32. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js +172 -0
  33. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js.map +1 -0
  34. package/dist/esm/components/CnChoicePickerWidget/index.js +27 -0
  35. package/dist/esm/components/CnChoicePickerWidget/index.js.map +1 -0
  36. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +1 -1
  37. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
  38. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
  39. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +2 -2
  40. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
  41. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
  42. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +65 -5
  43. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  44. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -1
  45. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  46. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +19 -11
  47. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  48. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +4 -2
  49. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  50. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +134 -4
  51. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  52. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +21 -4
  53. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  54. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +24 -0
  55. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  56. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +43 -4
  57. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  58. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js +1 -1
  59. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js.map +1 -1
  60. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js.map +1 -1
  61. package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue2.js +7 -7
  62. package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue2.js.map +1 -1
  63. package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue3.js.map +1 -1
  64. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +11 -0
  65. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js.map +1 -0
  66. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +867 -0
  67. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -0
  68. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +238 -0
  69. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -0
  70. package/dist/esm/components/CnFactsWidget/index.js +30 -0
  71. package/dist/esm/components/CnFactsWidget/index.js.map +1 -0
  72. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js +10 -0
  73. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js.map +1 -0
  74. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +626 -0
  75. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -0
  76. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +396 -0
  77. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -0
  78. package/dist/esm/components/CnFieldListWidget/index.js +29 -0
  79. package/dist/esm/components/CnFieldListWidget/index.js.map +1 -0
  80. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +302 -4
  81. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  82. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +417 -244
  83. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  84. package/dist/esm/components/CnFilesBrowser/filesBrowser.js +101 -1
  85. package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
  86. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +141 -1
  87. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
  88. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +65 -17
  89. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  90. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +4 -4
  91. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  92. package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +78 -3
  93. package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
  94. package/dist/esm/components/CnFormPage/CnFormPage.vue3.js +74 -49
  95. package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
  96. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +333 -73
  97. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  98. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +79 -17
  99. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  100. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +10 -0
  101. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js.map +1 -0
  102. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +99 -0
  103. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -0
  104. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +48 -0
  105. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -0
  106. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +7 -7
  107. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
  108. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
  109. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js +1 -1
  110. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js.map +1 -1
  111. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +65 -2
  112. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  113. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +6 -2
  114. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  115. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +63 -1
  116. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  117. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +8 -1
  118. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  119. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js +14 -0
  120. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js.map +1 -1
  121. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js +5 -1
  122. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js.map +1 -1
  123. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js +29 -1
  124. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
  125. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
  126. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js +10 -0
  127. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js.map +1 -0
  128. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js +174 -0
  129. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js.map +1 -0
  130. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js +75 -0
  131. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js.map +1 -0
  132. package/dist/esm/components/CnRadioCardsWidget/index.js +28 -0
  133. package/dist/esm/components/CnRadioCardsWidget/index.js.map +1 -0
  134. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +10 -0
  135. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js.map +1 -0
  136. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +411 -0
  137. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -0
  138. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +178 -0
  139. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -0
  140. package/dist/esm/components/CnRecordListWidget/index.js +31 -0
  141. package/dist/esm/components/CnRecordListWidget/index.js.map +1 -0
  142. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js +10 -0
  143. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js.map +1 -0
  144. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js +222 -0
  145. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js.map +1 -0
  146. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js +64 -0
  147. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js.map +1 -0
  148. package/dist/esm/components/CnSavedViewsWidget/index.js +30 -0
  149. package/dist/esm/components/CnSavedViewsWidget/index.js.map +1 -0
  150. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue.js +10 -0
  151. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue.js.map +1 -0
  152. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js +351 -0
  153. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js.map +1 -0
  154. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js +71 -0
  155. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js.map +1 -0
  156. package/dist/esm/components/CnSectionsWidget/index.js +35 -0
  157. package/dist/esm/components/CnSectionsWidget/index.js.map +1 -0
  158. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -1
  159. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -1
  160. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js +10 -0
  161. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js.map +1 -0
  162. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js +347 -0
  163. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js.map +1 -0
  164. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js +184 -0
  165. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js.map +1 -0
  166. package/dist/esm/components/CnSettingsFormWidget/index.js +27 -0
  167. package/dist/esm/components/CnSettingsFormWidget/index.js.map +1 -0
  168. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +81 -2
  169. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
  170. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
  171. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +87 -6
  172. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  173. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +1 -0
  174. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  175. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +84 -1
  176. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
  177. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +85 -41
  178. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
  179. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js +10 -0
  180. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js.map +1 -0
  181. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js +159 -0
  182. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js.map +1 -0
  183. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js +60 -0
  184. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js.map +1 -0
  185. package/dist/esm/components/CnSwitchRowsWidget/index.js +26 -0
  186. package/dist/esm/components/CnSwitchRowsWidget/index.js.map +1 -0
  187. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +168 -7
  188. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  189. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +86 -23
  190. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  191. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +80 -1
  192. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
  193. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +75 -32
  194. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
  195. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js +10 -0
  196. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js.map +1 -0
  197. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js +269 -0
  198. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js.map +1 -0
  199. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js +106 -0
  200. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js.map +1 -0
  201. package/dist/esm/components/CnToggleTableWidget/index.js +27 -0
  202. package/dist/esm/components/CnToggleTableWidget/index.js.map +1 -0
  203. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +11 -0
  204. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  205. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +7 -2
  206. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  207. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  208. package/dist/esm/components/CnWidgetObjectTable/dashboardRegistration.js +5 -0
  209. package/dist/esm/components/CnWidgetObjectTable/dashboardRegistration.js.map +1 -1
  210. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js +301 -6
  211. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
  212. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js +153 -55
  213. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
  214. package/dist/esm/composables/useFormDraft.js +140 -9
  215. package/dist/esm/composables/useFormDraft.js.map +1 -1
  216. package/dist/esm/composables/useListView.js +4 -4
  217. package/dist/esm/composables/useListView.js.map +1 -1
  218. package/dist/esm/index.js +21 -1
  219. package/dist/esm/index.js.map +1 -1
  220. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js +28 -20
  221. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js.map +1 -1
  222. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js +9 -0
  223. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js.map +1 -1
  224. package/dist/esm/integrations/builtin/field-inspection.js +21 -0
  225. package/dist/esm/integrations/builtin/field-inspection.js.map +1 -1
  226. package/dist/esm/integrations/offline/checklistSubmission.js +141 -0
  227. package/dist/esm/integrations/offline/checklistSubmission.js.map +1 -0
  228. package/dist/esm/integrations/offline/fieldCollectionHelpers.js +70 -5
  229. package/dist/esm/integrations/offline/fieldCollectionHelpers.js.map +1 -1
  230. package/dist/esm/integrations/offline/index.js +2 -1
  231. package/dist/esm/integrations/offline/index.js.map +1 -1
  232. package/dist/esm/integrations/offline/offlineDb.js +6 -1
  233. package/dist/esm/integrations/offline/offlineDb.js.map +1 -1
  234. package/dist/esm/integrations/offline/syncReplayService.js +8 -2
  235. package/dist/esm/integrations/offline/syncReplayService.js.map +1 -1
  236. package/dist/esm/l10n/en.json.js +20 -1
  237. package/dist/esm/l10n/en.json.js.map +1 -1
  238. package/dist/esm/l10n/nl.json.js +70 -2
  239. package/dist/esm/l10n/nl.json.js.map +1 -1
  240. package/dist/esm/nextcloud-vue.css +2192 -222
  241. package/dist/esm/utils/contentHeaderLink.js +51 -0
  242. package/dist/esm/utils/contentHeaderLink.js.map +1 -0
  243. package/dist/esm/utils/detailActionModel.js +60 -3
  244. package/dist/esm/utils/detailActionModel.js.map +1 -1
  245. package/dist/esm/utils/facets.js +75 -3
  246. package/dist/esm/utils/facets.js.map +1 -1
  247. package/dist/esm/utils/fetchFilterCounts.js +40 -5
  248. package/dist/esm/utils/fetchFilterCounts.js.map +1 -1
  249. package/dist/esm/utils/fillTemplate.js +120 -0
  250. package/dist/esm/utils/fillTemplate.js.map +1 -0
  251. package/dist/esm/utils/initials.js +32 -0
  252. package/dist/esm/utils/initials.js.map +1 -0
  253. package/dist/esm/utils/libraryWidgetKeys.js +11 -0
  254. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  255. package/dist/esm/utils/objectWrite.js +59 -0
  256. package/dist/esm/utils/objectWrite.js.map +1 -0
  257. package/dist/esm/utils/pageControls.js +81 -0
  258. package/dist/esm/utils/pageControls.js.map +1 -0
  259. package/dist/esm/utils/personalLenses.js +40 -3
  260. package/dist/esm/utils/personalLenses.js.map +1 -1
  261. package/dist/esm/utils/queryPresets.js +96 -0
  262. package/dist/esm/utils/queryPresets.js.map +1 -0
  263. package/dist/esm/utils/resolveFilterTokens.js +12 -1
  264. package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
  265. package/dist/esm/utils/resolveManifestTokens.js +2 -0
  266. package/dist/esm/utils/resolveManifestTokens.js.map +1 -1
  267. package/dist/esm/utils/schema.js +21 -0
  268. package/dist/esm/utils/schema.js.map +1 -1
  269. package/dist/esm/utils/sentinelTokens.js +22 -4
  270. package/dist/esm/utils/sentinelTokens.js.map +1 -1
  271. package/dist/esm/utils/userGroupsPeek.js +91 -0
  272. package/dist/esm/utils/userGroupsPeek.js.map +1 -0
  273. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  274. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  275. package/dist/esm/utils/visibleWhen.js +57 -5
  276. package/dist/esm/utils/visibleWhen.js.map +1 -1
  277. package/dist/esm/utils/widgetDispatch.js +7 -1
  278. package/dist/esm/utils/widgetDispatch.js.map +1 -1
  279. package/dist/esm/utils/widgetVisibility.js +4 -57
  280. package/dist/esm/utils/widgetVisibility.js.map +1 -1
  281. package/dist/nextcloud-vue.cjs.js +19708 -9301
  282. package/dist/nextcloud-vue.cjs.js.map +2 -2
  283. package/dist/nextcloud-vue.css +2192 -222
  284. package/l10n/en.json +20 -1
  285. package/l10n/nl.json +70 -2
  286. package/package.json +2 -1
  287. package/scripts/check-docs-mdx.mjs +108 -0
  288. package/src/components/CnActionButtons/CnActionButtons.vue +2 -0
  289. package/src/components/CnActionsBar/CnActionsBar.md +4 -0
  290. package/src/components/CnActionsBar/CnActionsBar.vue +48 -11
  291. package/src/components/CnAppRoot/CnAppRoot.md +4 -2
  292. package/src/components/CnAppRoot/CnAppRoot.vue +151 -59
  293. package/src/components/CnBannerWidget/CnBannerWidget.vue +244 -23
  294. package/src/components/CnCellRenderer/CnCellRenderer.md +2 -0
  295. package/src/components/CnCellRenderer/CnCellRenderer.vue +235 -5
  296. package/src/components/CnChipsFieldWidget/CnChipsFieldWidget.vue +433 -0
  297. package/src/components/CnChipsFieldWidget/index.js +25 -0
  298. package/src/components/CnChoicePickerWidget/CnChoicePickerWidget.vue +556 -0
  299. package/src/components/CnChoicePickerWidget/index.js +26 -0
  300. package/src/components/CnConversationThread/CnConversationWidget.vue +1 -1
  301. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +2 -2
  302. package/src/components/CnDashboardPage/CnDashboardPage.vue +78 -5
  303. package/src/components/CnDataTable/CnDataTable.vue +22 -12
  304. package/src/components/CnDetailPage/CnDetailPage.md +1 -1
  305. package/src/components/CnDetailPage/CnDetailPage.vue +152 -6
  306. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +66 -0
  307. package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +1 -1
  308. package/src/components/CnFacetSidebar/CnFacetSidebar.vue +7 -7
  309. package/src/components/CnFactsWidget/CnFactsWidget.vue +1190 -0
  310. package/src/components/CnFactsWidget/index.js +29 -0
  311. package/src/components/CnFieldListWidget/CnFieldListWidget.vue +1008 -0
  312. package/src/components/CnFieldListWidget/index.js +28 -0
  313. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +588 -117
  314. package/src/components/CnFilesBrowser/filesBrowser.js +100 -0
  315. package/src/components/CnFilesBrowser/filesBrowserColumns.js +140 -0
  316. package/src/components/CnFormDialog/CnFormDialog.md +9 -3
  317. package/src/components/CnFormDialog/CnFormDialog.vue +69 -21
  318. package/src/components/CnFormPage/CnFormPage.vue +99 -3
  319. package/src/components/CnIndexPage/CnIndexPage.vue +309 -9
  320. package/src/components/CnIndexPage/CnIndexPageWidgets.vue +169 -0
  321. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +7 -7
  322. package/src/components/CnNextStepCard/CnNextStepWidget.vue +1 -1
  323. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +69 -2
  324. package/src/components/CnObjectSidebar/CnFilesTab.vue +70 -1
  325. package/src/components/CnObjectSidebar/CnNotesTab.vue +16 -0
  326. package/src/components/CnPageRenderer/CnPageRenderer.vue +29 -1
  327. package/src/components/CnRadioCardsWidget/CnRadioCardsWidget.vue +313 -0
  328. package/src/components/CnRadioCardsWidget/index.js +27 -0
  329. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +652 -0
  330. package/src/components/CnRecordListWidget/index.js +30 -0
  331. package/src/components/CnSavedViewsWidget/CnSavedViewsWidget.vue +296 -0
  332. package/src/components/CnSavedViewsWidget/index.js +29 -0
  333. package/src/components/CnSectionsWidget/CnSectionsWidget.vue +489 -0
  334. package/src/components/CnSectionsWidget/index.js +34 -0
  335. package/src/components/CnSegmentedControl/CnSegmentedControl.vue +13 -0
  336. package/src/components/CnSettingsFormWidget/CnSettingsFormWidget.vue +555 -0
  337. package/src/components/CnSettingsFormWidget/index.js +26 -0
  338. package/src/components/CnStagesWidget/CnStagesWidget.vue +81 -2
  339. package/src/components/CnStatWidget/CnStatWidget.vue +88 -7
  340. package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +180 -3
  341. package/src/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue +226 -0
  342. package/src/components/CnSwitchRowsWidget/index.js +25 -0
  343. package/src/components/CnTabsWidget/CnTabsWidget.vue +263 -8
  344. package/src/components/CnTasksWidget/CnTasksWidget.vue +175 -15
  345. package/src/components/CnToggleTableWidget/CnToggleTableWidget.vue +386 -0
  346. package/src/components/CnToggleTableWidget/index.js +26 -0
  347. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +11 -0
  348. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +7 -2
  349. package/src/components/CnWidgetObjectTable/dashboardRegistration.js +5 -0
  350. package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +493 -11
  351. package/src/components/index.js +19 -0
  352. package/src/composables/__tests__/useFormDraft.spec.js +69 -0
  353. package/src/composables/useFormDraft.js +139 -8
  354. package/src/composables/useListView.js +4 -4
  355. package/src/css/form-field.css +14 -0
  356. package/src/css/index-page.css +71 -0
  357. package/src/css/kpi-card.css +32 -0
  358. package/src/css/look-board-detail.css +113 -2
  359. package/src/css/look-board-index.css +90 -8
  360. package/src/css/look-board.css +109 -2
  361. package/src/css/table.css +19 -0
  362. package/src/index.js +19 -0
  363. package/src/integrations/builtin/field-inspection/CnFieldInspectionCard.vue +35 -20
  364. package/src/integrations/builtin/field-inspection.js +21 -0
  365. package/src/integrations/offline/checklistSubmission.js +137 -0
  366. package/src/integrations/offline/fieldCollectionHelpers.js +69 -4
  367. package/src/integrations/offline/index.js +3 -0
  368. package/src/integrations/offline/offlineDb.js +6 -1
  369. package/src/integrations/offline/syncReplayService.js +8 -2
  370. package/src/schemas/app-manifest-v2.schema.json +196 -13
  371. package/src/utils/contentHeaderLink.js +48 -0
  372. package/src/utils/detailActionModel.js +59 -2
  373. package/src/utils/facets.js +74 -2
  374. package/src/utils/fetchFilterCounts.js +39 -4
  375. package/src/utils/fillTemplate.js +116 -0
  376. package/src/utils/initials.js +29 -0
  377. package/src/utils/libraryWidgetKeys.js +11 -0
  378. package/src/utils/objectWrite.js +55 -0
  379. package/src/utils/pageControls.js +78 -0
  380. package/src/utils/personalLenses.js +39 -2
  381. package/src/utils/queryPresets.js +93 -0
  382. package/src/utils/resolveFilterTokens.js +14 -1
  383. package/src/utils/resolveManifestTokens.js +2 -0
  384. package/src/utils/schema.js +21 -0
  385. package/src/utils/sentinelTokens.js +22 -4
  386. package/src/utils/userGroupsPeek.js +87 -0
  387. package/src/utils/validateManifestV2.compiled.js +3 -3
  388. package/src/utils/visibleWhen.js +56 -4
  389. package/src/utils/widgetDispatch.js +7 -1
  390. package/src/utils/widgetVisibility.js +7 -56
@@ -3,7 +3,8 @@ import { NcButton, NcLoadingIcon, NcEmptyContent } from '@nextcloud/vue';
3
3
  import ClipboardCheckOutline from 'vue-material-design-icons/ClipboardCheckOutline.vue';
4
4
  import __sfc_main from '../../../components/CnDetailCard/CnDetailCard.vue.js';
5
5
  import __sfc_main$1 from '../../../components/CnOfflineQueue/CnOfflineQueue.vue.js';
6
- import { syncIndicator, checklistProgress, validateChecklistAnswers, classifyGps } from '../../offline/fieldCollectionHelpers.js';
6
+ import { buildChecklistSubmission } from '../../offline/checklistSubmission.js';
7
+ import { syncIndicator, checklistProgress, normaliseChecklistTemplate, validateChecklistAnswers, classifyGps } from '../../offline/fieldCollectionHelpers.js';
7
8
  import { resolveDeviceId, getPlannedItems, getPlanningMeta, countPending, countStuck, storePlanning, getCachedObject, enqueueMutation } from '../../offline/offlineDb.js';
8
9
  import { fetchPlanning, fetchReferences } from '../../offline/planningFetch.js';
9
10
  import { drainQueue } from '../../offline/syncReplayService.js';
@@ -73,6 +74,8 @@ var script = {
73
74
  activeTemplate: null,
74
75
  answers: {},
75
76
  errors: [],
77
+ /** Why the last save could not be queued, in words. */
78
+ saveError: '',
76
79
  }
77
80
  },
78
81
 
@@ -286,8 +289,13 @@ var script = {
286
289
  this.errors = [];
287
290
  const cfg = this.config;
288
291
  const templateRef = item?.[cfg.templateRefField];
292
+ // The app's template shape (sections, its own item keys and type
293
+ // words) is read into the leaf's flat item list here, once.
289
294
  this.activeTemplate = templateRef
290
- ? await getCachedObject(this.effectiveRegister, cfg.referenceSchema, 'references', String(templateRef))
295
+ ? normaliseChecklistTemplate(
296
+ await getCachedObject(this.effectiveRegister, cfg.referenceSchema, 'references', String(templateRef)),
297
+ cfg,
298
+ )
291
299
  : null;
292
300
  const initial = {};
293
301
  for (const q of (this.activeTemplate?.items || [])) {
@@ -301,6 +309,7 @@ var script = {
301
309
  this.activeTemplate = null;
302
310
  this.answers = {};
303
311
  this.errors = [];
312
+ this.saveError = '';
304
313
  },
305
314
 
306
315
  /**
@@ -322,7 +331,9 @@ var script = {
322
331
  },
323
332
 
324
333
  /**
325
- * Validate answers, then queue a `create` mutation for the result.
334
+ * Validate answers, then queue the run: an object create on
335
+ * `resultSchema`, or, when the config names a `resultEndpoint`, one
336
+ * `submit` to that endpoint.
326
337
  *
327
338
  * @return {Promise<void>}
328
339
  */
@@ -336,26 +347,22 @@ var script = {
336
347
  try {
337
348
  const cfg = this.config;
338
349
  const gps = await this.captureGps();
339
- const gpsClass = classifyGps(gps);
340
- const now = new Date().toISOString();
341
- const items = this.activeTemplate.items.map((q) => ({
342
- questionId: q.questionId,
343
- answer: this.answers[q.questionId].answer,
344
- evidenceRefs: this.answers[q.questionId].evidenceRefs,
345
- answeredAt: now,
346
- gpsAtAnswer: gps ? { ...gps, source: gpsClass.source } : { source: 'sensorless' },
347
- }));
348
- const payload = {
349
- inspectionRef: this.itemId(this.activeItem),
350
- checklistTemplateRef: this.activeTemplate.id ?? this.activeTemplate['@self']?.id,
351
- items,
352
- };
350
+ const submission = buildChecklistSubmission(cfg, {
351
+ plannedItem: this.activeItem,
352
+ template: this.activeTemplate,
353
+ answers: this.answers,
354
+ gps,
355
+ gpsSource: classifyGps(gps).source,
356
+ capturedAt: new Date().toISOString(),
357
+ capturedOffline: this.offline,
358
+ });
353
359
  await enqueueMutation({
354
360
  deviceId: this.deviceId,
355
- operationType: 'create',
361
+ operationType: submission.operationType,
356
362
  register: this.effectiveRegister,
357
- schema: cfg.resultSchema,
358
- payload,
363
+ schema: submission.schema,
364
+ endpoint: submission.endpoint,
365
+ payload: submission.payload,
359
366
  });
360
367
  this.pendingCount = await countPending(this.deviceId);
361
368
  this.stuckCount = await countStuck(this.deviceId);
@@ -364,6 +371,7 @@ var script = {
364
371
  await this.drain();
365
372
  }
366
373
  } catch (e) {
374
+ this.saveError = e?.message || translate('nextcloud-vue', 'The answers could not be saved on this device.');
367
375
  // eslint-disable-next-line no-console
368
376
  console.error('[CnFieldInspectionCard] saveChecklist failed', e);
369
377
  } finally {
@@ -1 +1 @@
1
- {"version":3,"file":"CnFieldInspectionCard.vue2.js","sources":["../../../../../src/integrations/builtin/field-inspection/CnFieldInspectionCard.vue"],"sourcesContent":["<!--\n - SPDX-License-Identifier: EUPL-1.2\n - SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n -\n - CnFieldInspectionCard — surface-aware widget for the `field-inspection`\n - integration leaf (offline field data-collection).\n -\n - Drives the generic offline core (src/integrations/offline): a\n - \"Synchronise day\" action fetches today's planned items via the standard OR\n - object API (planningFetch) into the IndexedDB cache, the planning list lets\n - the user open and complete a checklist offline, and saved answers are queued\n - as OR object mutations that replay on reconnect. The sync-state indicator\n - (online / all-synced / queued / conflict) reflects the pending queue.\n -\n - Generic by construction: the checklist items, the planned-items query and\n - the schema names all come from the integration's `offlineConfig` (resolved\n - from the registry by id) — never from app-specific code in this widget.\n-->\n<template>\n\t<CnDetailCard :title=\"cardTitle\" :icon=\"cardIcon\" :collapsible=\"collapsible\">\n\t\t<div class=\"cn-field-inspection\" data-testid=\"cn-field-inspection-card\">\n\t\t\t<div class=\"cn-field-inspection__sync\" :class=\"`cn-field-inspection__sync--${indicator.tone}`\" data-testid=\"cn-fi-sync-indicator\">\n\t\t\t\t<span class=\"cn-field-inspection__dot\" aria-hidden=\"true\" />\n\t\t\t\t<span>{{ indicator.text }}</span>\n\t\t\t</div>\n\n\t\t\t<div class=\"cn-field-inspection__actions\">\n\t\t\t\t<NcButton variant=\"primary\"\n\t\t\t\t\tdata-testid=\"cn-fi-sync-day\"\n\t\t\t\t\t:disabled=\"syncing || offline\"\n\t\t\t\t\t@click=\"syncDay\">\n\t\t\t\t\t{{ syncing ? t('nextcloud-vue', 'Synchronising…') : t('nextcloud-vue', 'Synchronise day') }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton v-if=\"pendingCount > 0 && !offline\"\n\t\t\t\t\tdata-testid=\"cn-fi-drain-queue\"\n\t\t\t\t\t:disabled=\"syncing\"\n\t\t\t\t\t@click=\"drain\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Sync {n} pending changes', { n: pendingCount }) }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton v-if=\"pendingCount > 0 || stuckCount > 0\"\n\t\t\t\t\tdata-testid=\"cn-fi-open-queue\"\n\t\t\t\t\t@click=\"showQueue = !showQueue\">\n\t\t\t\t\t{{ showQueue\n\t\t\t\t\t\t? t('nextcloud-vue', 'Hide what is waiting')\n\t\t\t\t\t\t: t('nextcloud-vue', 'See what is waiting') }}\n\t\t\t\t</NcButton>\n\t\t\t\t<!-- 🔴 AN EXPIRED PLANNING USED TO READ AS A CURRENT ONE. This\n\t\t\t\t line only ever said \"Ready offline until\", in the past tense\n\t\t\t\t or not, so a planning downloaded three days ago looked the\n\t\t\t\t same as one downloaded an hour ago. In the field there is\n\t\t\t\t nothing else to check it against. -->\n\t\t\t\t<span v-if=\"planningMeta\" class=\"cn-field-inspection__meta\" data-testid=\"cn-fi-planning-meta\">\n\t\t\t\t\t{{ planningIsStale\n\t\t\t\t\t\t? t('nextcloud-vue', 'This planning is out of date. It was downloaded on {time}.', { time: formatTime(planningMeta.syncedAt) })\n\t\t\t\t\t\t: t('nextcloud-vue', 'Ready offline until {time}', { time: formatTime(planningMeta.expiresAt) }) }}\n\t\t\t\t</span>\n\t\t\t</div>\n\n\t\t\t<!-- The card links to the queue rather than owning a second copy\n\t\t\t of it: one component, one set of statuses, one set of words. -->\n\t\t\t<CnOfflineQueue v-if=\"showQueue\" :deviceId=\"deviceId\" @requeued=\"loadLocal\" />\n\n\t\t\t<NcLoadingIcon v-if=\"loading\" :size=\"24\" />\n\n\t\t\t<NcEmptyContent v-else-if=\"!activeItem && plannedItems.length === 0\"\n\t\t\t\tdata-testid=\"cn-fi-empty\"\n\t\t\t\t:name=\"t('nextcloud-vue', 'No items planned')\"\n\t\t\t\t:description=\"t('nextcloud-vue', 'Tap “Synchronise day” while online to download your planning.')\" />\n\n\t\t\t<!-- Planning list -->\n\t\t\t<ul v-else-if=\"!activeItem\" class=\"cn-field-inspection__list\" data-testid=\"cn-fi-list\">\n\t\t\t\t<li v-for=\"item in plannedItems\" :key=\"itemId(item)\" class=\"cn-field-inspection__row\">\n\t\t\t\t\t<button type=\"button\"\n\t\t\t\t\t\tclass=\"cn-field-inspection__row-btn\"\n\t\t\t\t\t\t:data-testid=\"`cn-fi-open-${itemId(item)}`\"\n\t\t\t\t\t\t@click=\"openItem(item)\">\n\t\t\t\t\t\t<span class=\"cn-field-inspection__row-title\">{{ itemTitle(item) }}</span>\n\t\t\t\t\t\t<span class=\"cn-field-inspection__row-status\">{{ itemStatus(item) }}</span>\n\t\t\t\t\t</button>\n\t\t\t\t</li>\n\t\t\t</ul>\n\n\t\t\t<!-- Checklist completion for one item -->\n\t\t\t<div v-else class=\"cn-field-inspection__checklist\" data-testid=\"cn-fi-checklist\">\n\t\t\t\t<div class=\"cn-field-inspection__checklist-head\">\n\t\t\t\t\t<NcButton variant=\"tertiary\" data-testid=\"cn-fi-back\" @click=\"closeItem\">\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Back') }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<strong>{{ itemTitle(activeItem) }}</strong>\n\t\t\t\t</div>\n\n\t\t\t\t<p v-if=\"activeTemplate\" class=\"cn-field-inspection__progress\" data-testid=\"cn-fi-progress\">\n\t\t\t\t\t{{ t('nextcloud-vue', '{done} of {total} questions completed', { done: progress.done, total: progress.total }) }}\n\t\t\t\t</p>\n\n\t\t\t\t<form v-if=\"activeTemplate\" @submit.prevent=\"saveChecklist\">\n\t\t\t\t\t<fieldset v-for=\"q in activeTemplate.items\" :key=\"q.questionId\" class=\"cn-field-inspection__question\">\n\t\t\t\t\t\t<legend>\n\t\t\t\t\t\t\t{{ q.text }}<span v-if=\"q.required\" class=\"cn-field-inspection__required\" aria-hidden=\"true\"> *</span>\n\t\t\t\t\t\t</legend>\n\t\t\t\t\t\t<select v-if=\"q.type === 'yes_no'\"\n\t\t\t\t\t\t\tv-model=\"answers[q.questionId].answer\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-fi-answer-${q.questionId}`\"\n\t\t\t\t\t\t\tclass=\"cn-field-inspection__input\">\n\t\t\t\t\t\t\t<option value=\"\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', '— choose —') }}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t<option value=\"yes\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Yes') }}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t<option value=\"no\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'No') }}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t<option value=\"na\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'N/A') }}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t</select>\n\t\t\t\t\t\t<textarea v-else\n\t\t\t\t\t\t\tv-model=\"answers[q.questionId].answer\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-fi-answer-${q.questionId}`\"\n\t\t\t\t\t\t\tclass=\"cn-field-inspection__input\"\n\t\t\t\t\t\t\trows=\"2\" />\n\t\t\t\t\t\t<span v-if=\"errorFor(q.questionId)\" class=\"cn-field-inspection__error\" :data-testid=\"`cn-fi-error-${q.questionId}`\">\n\t\t\t\t\t\t\t{{ errorFor(q.questionId) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</fieldset>\n\n\t\t\t\t\t<!-- `type=\"submit\"` (native HTML type) + `variant=\"primary\"` (style) —\n\t\t\t\t\t @nextcloud/vue 9 split what Vue-2-era code conflated: `type` is now\n\t\t\t\t\t the native button type (`ButtonType`), and the old style-variant\n\t\t\t\t\t prop is `variant`. This line used to pass BOTH wrong: `native-type`\n\t\t\t\t\t (not a real prop — the button silently defaulted to `type=\"button\"`\n\t\t\t\t\t and never fired the form's native submit) and `variant=\"primary\"`\n\t\t\t\t\t (an invalid `ButtonType`, so the visual primary style never applied\n\t\t\t\t\t either). See CnFormPage.vue's inline comment for the submit-button\n\t\t\t\t\t half of this bug in detail. -->\n\t\t\t\t\t<NcButton type=\"submit\"\n\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\tdata-testid=\"cn-fi-save\"\n\t\t\t\t\t\t:disabled=\"saving\">\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Save answers offline') }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</form>\n\n\t\t\t\t<NcEmptyContent v-else\n\t\t\t\t\tdata-testid=\"cn-fi-no-template\"\n\t\t\t\t\t:name=\"t('nextcloud-vue', 'Checklist not available offline')\"\n\t\t\t\t\t:description=\"t('nextcloud-vue', 'Synchronise the day while online to download this checklist.')\" />\n\t\t\t</div>\n\t\t</div>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'\nimport ClipboardCheckOutline from 'vue-material-design-icons/ClipboardCheckOutline.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport CnOfflineQueue from '../../../components/CnOfflineQueue/index.js'\nimport { checklistProgress, classifyGps, syncIndicator, validateChecklistAnswers } from '../../offline/fieldCollectionHelpers.js'\nimport {\n\tcountPending,\n\tcountStuck,\n\tenqueueMutation,\n\tgetCachedObject,\n\tgetPlannedItems,\n\tgetPlanningMeta,\n\tresolveDeviceId,\n\tstorePlanning,\n} from '../../offline/offlineDb.js'\nimport { fetchPlanning, fetchReferences } from '../../offline/planningFetch.js'\nimport { drainQueue } from '../../offline/syncReplayService.js'\nimport { DEFAULT_FIELD_INSPECTION_CONFIG } from '../field-inspection.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\n\n/**\n * CnFieldInspectionCard — surface-aware offline field data-collection widget.\n *\n * Renders the offline planning list and inline checklist completion across the\n * dashboard / detail surfaces, driven entirely by the generic offline core and\n * the integration's `offlineConfig`.\n */\nexport default {\n\tname: 'CnFieldInspectionCard',\n\n\tcomponents: { CnDetailCard, CnOfflineQueue, NcButton, NcLoadingIcon, NcEmptyContent },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'field-inspection'`). */\n\t\tintegrationId: { type: String, default: 'field-inspection' },\n\t\t/* eslint-disable vue/no-unused-properties -- every integration surface is handed `surface` by its host; this card renders one layout and does not branch on it */\n\t\t/** Rendering surface (AD-19). */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (s) => VALID_SURFACES.includes(s),\n\t\t},\n\t\t/* eslint-enable vue/no-unused-properties */\n\n\t\t/** Object context `{ register, schema, objectId }` forwarded by the host page. */\n\t\tintegrationContext: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** OpenRegister register id (slug or uuid) — falls back to integrationContext. */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid) — falls back to integrationContext. */\n\t\tschema: { type: String, default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Field inspections') },\n\t\t/** Optional Material Design Icon component for the card header. */\n\t\ticon: { type: [Object, Function], default: () => ClipboardCheckOutline },\n\t\t/** Whether the card body is collapsible. */\n\t\tcollapsible: { type: Boolean, default: false },\n\t\t/** Optional explicit assignee uid for the planning query (default: current user). */\n\t\tassignee: { type: String, default: '' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tplannedItems: [],\n\t\t\tplanningMeta: null,\n\t\t\tpendingCount: 0,\n\t\t\t/** Operations that will not send again without somebody acting. */\n\t\t\tstuckCount: 0,\n\t\t\t/** Whether the queue list is open under the card. */\n\t\t\tshowQueue: false,\n\t\t\tloading: true,\n\t\t\tsyncing: false,\n\t\t\tsaving: false,\n\t\t\toffline: typeof navigator !== 'undefined' ? navigator.onLine === false : false,\n\t\t\tdeviceId: resolveDeviceId(),\n\t\t\tactiveItem: null,\n\t\t\tactiveTemplate: null,\n\t\t\tanswers: {},\n\t\t\terrors: [],\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardTitle() {\n\t\t\treturn this.title || this.integrationId\n\t\t},\n\n\t\tcardIcon() {\n\t\t\treturn this.icon\n\t\t},\n\n\t\t/**\n\t\t * Resolved offline config from the registered descriptor on the shared\n\t\t * registry, falling back to the canonical defaults. A consuming app's\n\t\t * override supplies its own schema names / filter fields here. Read from\n\t\t * the global registry (not a static import) to avoid an import cycle.\n\t\t *\n\t\t * @return {object} The offline config.\n\t\t */\n\t\tconfig() {\n\t\t\tconst registry = (typeof window !== 'undefined'\n\t\t\t\t&& window.OCA && window.OCA.OpenRegister && window.OCA.OpenRegister.integrations) || null\n\t\t\tconst entry = (registry && typeof registry.get === 'function') ? registry.get(this.integrationId) : null\n\t\t\treturn (entry && entry.offlineConfig) || DEFAULT_FIELD_INSPECTION_CONFIG\n\t\t},\n\n\t\t/**\n\t\t * Effective register (prop wins, else integrationContext).\n\t\t *\n\t\t * @return {string} The register id.\n\t\t */\n\t\teffectiveRegister() {\n\t\t\treturn this.register || this.integrationContext.register || ''\n\t\t},\n\n\t\t/**\n\t\t * Effective planned-items schema (config wins, else context/prop schema).\n\t\t *\n\t\t * @return {string} The schema id.\n\t\t */\n\t\teffectiveSchema() {\n\t\t\treturn this.config.plannedSchema || this.schema || this.integrationContext.schema || ''\n\t\t},\n\n\t\t/**\n\t\t * Whether the cached planning has passed its offline lifetime.\n\t\t *\n\t\t * @return {boolean} True when it is past `expiresAt`.\n\t\t */\n\t\tplanningIsStale() {\n\t\t\tconst expiresAt = this.planningMeta?.expiresAt\n\t\t\tif (typeof expiresAt !== 'string' || expiresAt === '') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst expiry = Date.parse(expiresAt)\n\t\t\treturn Number.isNaN(expiry) === false && expiry < Date.now()\n\t\t},\n\n\t\t/**\n\t\t * Where a conflict found during a drain is filed.\n\t\t *\n\t\t * @return {object} The drain config.\n\t\t */\n\t\tdrainConfig() {\n\t\t\treturn {\n\t\t\t\tregister: this.config.register || this.effectiveRegister,\n\t\t\t\tconflictSchema: this.config.conflictSchema || '',\n\t\t\t}\n\t\t},\n\n\t\tindicator() {\n\t\t\treturn syncIndicator(this.pendingCount, this.offline === false, this.stuckCount)\n\t\t},\n\n\t\tprogress() {\n\t\t\treturn checklistProgress(this.activeTemplate, this.answers)\n\t\t},\n\t},\n\n\twatch: {\n\t\tintegrationContext: { immediate: true, handler() {\n\t\t\tthis.loadLocal()\n\t\t} },\n\t},\n\n\t/**\n\t * Wire online/offline listeners (auto-drain on reconnect) + load local state.\n\t *\n\t * @return {Promise<void>}\n\t */\n\tasync mounted() {\n\t\twindow.addEventListener('online', this.onOnline)\n\t\twindow.addEventListener('offline', this.onOffline)\n\t\tawait this.loadLocal()\n\t},\n\n\t/**\n\t * Detach the online/offline listeners.\n\t *\n\t * @return {void}\n\t */\n\tbeforeUnmount() {\n\t\twindow.removeEventListener('online', this.onOnline)\n\t\twindow.removeEventListener('offline', this.onOffline)\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\titemId(item) {\n\t\t\treturn String(item?.id ?? item?.['@self']?.id ?? item?.uuid ?? '')\n\t\t},\n\n\t\titemTitle(item) {\n\t\t\treturn item?.[this.config.titleField] || this.itemId(item)\n\t\t},\n\n\t\titemStatus(item) {\n\t\t\tconst status = item?.status || 'planned'\n\t\t\tconst map = {\n\t\t\t\tplanned: t('nextcloud-vue', 'Planned'),\n\t\t\t\tin_progress: t('nextcloud-vue', 'In progress'),\n\t\t\t\tsynced: t('nextcloud-vue', 'Synced'),\n\t\t\t\tconflict: t('nextcloud-vue', 'Conflict'),\n\t\t\t}\n\t\t\treturn map[status] || status\n\t\t},\n\n\t\tformatTime(iso) {\n\t\t\tif (!iso) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn new Date(iso).toLocaleString()\n\t\t},\n\n\t\terrorFor(questionId) {\n\t\t\tconst e = this.errors.find((x) => x.questionId === questionId)\n\t\t\treturn e ? e.message : ''\n\t\t},\n\n\t\t/**\n\t\t * Load cached planning + pending count from IndexedDB.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadLocal() {\n\t\t\tif (!this.effectiveRegister || !this.effectiveSchema) {\n\t\t\t\tthis.loading = false\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\ttry {\n\t\t\t\tthis.plannedItems = await getPlannedItems(this.effectiveRegister, this.effectiveSchema)\n\t\t\t\tthis.planningMeta = await getPlanningMeta(this.effectiveRegister, this.effectiveSchema)\n\t\t\t\tthis.pendingCount = await countPending(this.deviceId)\n\t\t\t\tthis.stuckCount = await countStuck(this.deviceId)\n\t\t\t} catch (e) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFieldInspectionCard] loadLocal failed', e)\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Download today's planning (planned items + referenced templates) via\n\t\t * the standard OR object API and store it offline.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync syncDay() {\n\t\t\tthis.syncing = true\n\t\t\ttry {\n\t\t\t\tconst cfg = this.config\n\t\t\t\tconst items = await fetchPlanning({\n\t\t\t\t\tregister: this.effectiveRegister,\n\t\t\t\t\tschema: this.effectiveSchema,\n\t\t\t\t\tassigneeField: cfg.assigneeField,\n\t\t\t\t\tassignee: this.assignee || (typeof OC !== 'undefined' && OC.getCurrentUser ? OC.getCurrentUser().uid : ''),\n\t\t\t\t\tdateField: cfg.dateField,\n\t\t\t\t})\n\t\t\t\tconst references = cfg.referenceSchema\n\t\t\t\t\t? await fetchReferences({ register: this.effectiveRegister, referenceSchema: cfg.referenceSchema })\n\t\t\t\t\t: []\n\t\t\t\tawait storePlanning({\n\t\t\t\t\tregister: this.effectiveRegister,\n\t\t\t\t\tschema: this.effectiveSchema,\n\t\t\t\t\titems,\n\t\t\t\t\treferences,\n\t\t\t\t\treferenceSchema: cfg.referenceSchema,\n\t\t\t\t})\n\t\t\t\tawait this.loadLocal()\n\t\t\t} catch (e) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFieldInspectionCard] syncDay failed', e)\n\t\t\t} finally {\n\t\t\t\tthis.syncing = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Open one planned item and load its checklist template from cache.\n\t\t *\n\t\t * @param {object} item The planned item.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync openItem(item) {\n\t\t\tthis.activeItem = item\n\t\t\tthis.errors = []\n\t\t\tconst cfg = this.config\n\t\t\tconst templateRef = item?.[cfg.templateRefField]\n\t\t\tthis.activeTemplate = templateRef\n\t\t\t\t? await getCachedObject(this.effectiveRegister, cfg.referenceSchema, 'references', String(templateRef))\n\t\t\t\t: null\n\t\t\tconst initial = {}\n\t\t\tfor (const q of (this.activeTemplate?.items || [])) {\n\t\t\t\tinitial[q.questionId] = { answer: '', evidenceRefs: [] }\n\t\t\t}\n\t\t\tthis.answers = initial\n\t\t},\n\n\t\tcloseItem() {\n\t\t\tthis.activeItem = null\n\t\t\tthis.activeTemplate = null\n\t\t\tthis.answers = {}\n\t\t\tthis.errors = []\n\t\t},\n\n\t\t/**\n\t\t * Capture the current GPS fix (tagged on every answer).\n\t\t *\n\t\t * @return {Promise<object|null>} The GPS reading or null.\n\t\t */\n\t\tasync captureGps() {\n\t\t\tif (typeof navigator === 'undefined' || !navigator.geolocation) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn await new Promise((resolve) => {\n\t\t\t\tnavigator.geolocation.getCurrentPosition(\n\t\t\t\t\t(pos) => resolve({ lat: pos.coords.latitude, lon: pos.coords.longitude, accuracy: pos.coords.accuracy }),\n\t\t\t\t\t() => resolve(null),\n\t\t\t\t\t{ timeout: 8000 },\n\t\t\t\t)\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Validate answers, then queue a `create` mutation for the result.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync saveChecklist() {\n\t\t\tconst result = validateChecklistAnswers(this.activeTemplate, this.answers)\n\t\t\tthis.errors = result.errors\n\t\t\tif (result.valid === false) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.saving = true\n\t\t\ttry {\n\t\t\t\tconst cfg = this.config\n\t\t\t\tconst gps = await this.captureGps()\n\t\t\t\tconst gpsClass = classifyGps(gps)\n\t\t\t\tconst now = new Date().toISOString()\n\t\t\t\tconst items = this.activeTemplate.items.map((q) => ({\n\t\t\t\t\tquestionId: q.questionId,\n\t\t\t\t\tanswer: this.answers[q.questionId].answer,\n\t\t\t\t\tevidenceRefs: this.answers[q.questionId].evidenceRefs,\n\t\t\t\t\tansweredAt: now,\n\t\t\t\t\tgpsAtAnswer: gps ? { ...gps, source: gpsClass.source } : { source: 'sensorless' },\n\t\t\t\t}))\n\t\t\t\tconst payload = {\n\t\t\t\t\tinspectionRef: this.itemId(this.activeItem),\n\t\t\t\t\tchecklistTemplateRef: this.activeTemplate.id ?? this.activeTemplate['@self']?.id,\n\t\t\t\t\titems,\n\t\t\t\t}\n\t\t\t\tawait enqueueMutation({\n\t\t\t\t\tdeviceId: this.deviceId,\n\t\t\t\t\toperationType: 'create',\n\t\t\t\t\tregister: this.effectiveRegister,\n\t\t\t\t\tschema: cfg.resultSchema,\n\t\t\t\t\tpayload,\n\t\t\t\t})\n\t\t\t\tthis.pendingCount = await countPending(this.deviceId)\n\t\t\t\tthis.stuckCount = await countStuck(this.deviceId)\n\t\t\t\tthis.closeItem()\n\t\t\t\tif (this.offline === false) {\n\t\t\t\t\tawait this.drain()\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFieldInspectionCard] saveChecklist failed', e)\n\t\t\t} finally {\n\t\t\t\tthis.saving = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Drain the local mutation queue against the OR object API.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync drain() {\n\t\t\tthis.syncing = true\n\t\t\ttry {\n\t\t\t\tawait drainQueue(this.deviceId, this.drainConfig)\n\t\t\t\tthis.pendingCount = await countPending(this.deviceId)\n\t\t\t\tthis.stuckCount = await countStuck(this.deviceId)\n\t\t\t} catch (e) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFieldInspectionCard] drain failed', e)\n\t\t\t} finally {\n\t\t\t\tthis.syncing = false\n\t\t\t}\n\t\t},\n\n\t\tonOnline() {\n\t\t\tthis.offline = false\n\t\t\tthis.drain()\n\t\t},\n\n\t\tonOffline() {\n\t\t\tthis.offline = true\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-field-inspection {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tpadding: 4px 0;\n}\n\n.cn-field-inspection__sync {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.9em;\n}\n\n.cn-field-inspection__dot {\n\twidth: 10px;\n\theight: 10px;\n\tborder-radius: 50%;\n}\n\n.cn-field-inspection__sync--success .cn-field-inspection__dot { background: var(--color-success); }\n\n.cn-field-inspection__sync--warning .cn-field-inspection__dot { background: var(--color-warning); }\n\n.cn-field-inspection__sync--error .cn-field-inspection__dot { background: var(--color-error); }\n\n.cn-field-inspection__actions {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n\tflex-wrap: wrap;\n}\n\n.cn-field-inspection__meta {\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-field-inspection__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n}\n\n.cn-field-inspection__row-btn {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n\twidth: 100%;\n\tpadding: 12px 14px;\n\tmin-height: 44px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large);\n\tbackground: var(--color-main-background);\n\tcursor: pointer;\n\tcolor: var(--color-main-text);\n}\n\n.cn-field-inspection__row-btn:hover {\n\tbackground: var(--color-background-hover);\n}\n\n.cn-field-inspection__row-status {\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-field-inspection__checklist-head {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n}\n\n.cn-field-inspection__progress {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-field-inspection__question {\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tpadding: 12px;\n\tmargin-bottom: 12px;\n}\n\n.cn-field-inspection__input {\n\twidth: 100%;\n\tmin-height: 44px;\n\tmargin-top: 6px;\n}\n\n.cn-field-inspection__required { color: var(--color-error); }\n\n.cn-field-inspection__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.85em;\n\tdisplay: block;\n\tmargin-top: 4px;\n}\n</style>\n"],"names":["CnDetailCard","CnOfflineQueue","t"],"mappings":"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"}
1
+ {"version":3,"file":"CnFieldInspectionCard.vue2.js","sources":["../../../../../src/integrations/builtin/field-inspection/CnFieldInspectionCard.vue"],"sourcesContent":["<!--\n - SPDX-License-Identifier: EUPL-1.2\n - SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n -\n - CnFieldInspectionCard — surface-aware widget for the `field-inspection`\n - integration leaf (offline field data-collection).\n -\n - Drives the generic offline core (src/integrations/offline): a\n - \"Synchronise day\" action fetches today's planned items via the standard OR\n - object API (planningFetch) into the IndexedDB cache, the planning list lets\n - the user open and complete a checklist offline, and saved answers are queued\n - as OR object mutations that replay on reconnect. The sync-state indicator\n - (online / all-synced / queued / conflict) reflects the pending queue.\n -\n - Generic by construction: the checklist items, the planned-items query and\n - the schema names all come from the integration's `offlineConfig` (resolved\n - from the registry by id) — never from app-specific code in this widget.\n-->\n<template>\n\t<CnDetailCard :title=\"cardTitle\" :icon=\"cardIcon\" :collapsible=\"collapsible\">\n\t\t<div class=\"cn-field-inspection\" data-testid=\"cn-field-inspection-card\">\n\t\t\t<div class=\"cn-field-inspection__sync\" :class=\"`cn-field-inspection__sync--${indicator.tone}`\" data-testid=\"cn-fi-sync-indicator\">\n\t\t\t\t<span class=\"cn-field-inspection__dot\" aria-hidden=\"true\" />\n\t\t\t\t<span>{{ indicator.text }}</span>\n\t\t\t</div>\n\n\t\t\t<div class=\"cn-field-inspection__actions\">\n\t\t\t\t<NcButton variant=\"primary\"\n\t\t\t\t\tdata-testid=\"cn-fi-sync-day\"\n\t\t\t\t\t:disabled=\"syncing || offline\"\n\t\t\t\t\t@click=\"syncDay\">\n\t\t\t\t\t{{ syncing ? t('nextcloud-vue', 'Synchronising…') : t('nextcloud-vue', 'Synchronise day') }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton v-if=\"pendingCount > 0 && !offline\"\n\t\t\t\t\tdata-testid=\"cn-fi-drain-queue\"\n\t\t\t\t\t:disabled=\"syncing\"\n\t\t\t\t\t@click=\"drain\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Sync {n} pending changes', { n: pendingCount }) }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton v-if=\"pendingCount > 0 || stuckCount > 0\"\n\t\t\t\t\tdata-testid=\"cn-fi-open-queue\"\n\t\t\t\t\t@click=\"showQueue = !showQueue\">\n\t\t\t\t\t{{ showQueue\n\t\t\t\t\t\t? t('nextcloud-vue', 'Hide what is waiting')\n\t\t\t\t\t\t: t('nextcloud-vue', 'See what is waiting') }}\n\t\t\t\t</NcButton>\n\t\t\t\t<!-- 🔴 AN EXPIRED PLANNING USED TO READ AS A CURRENT ONE. This\n\t\t\t\t line only ever said \"Ready offline until\", in the past tense\n\t\t\t\t or not, so a planning downloaded three days ago looked the\n\t\t\t\t same as one downloaded an hour ago. In the field there is\n\t\t\t\t nothing else to check it against. -->\n\t\t\t\t<span v-if=\"planningMeta\" class=\"cn-field-inspection__meta\" data-testid=\"cn-fi-planning-meta\">\n\t\t\t\t\t{{ planningIsStale\n\t\t\t\t\t\t? t('nextcloud-vue', 'This planning is out of date. It was downloaded on {time}.', { time: formatTime(planningMeta.syncedAt) })\n\t\t\t\t\t\t: t('nextcloud-vue', 'Ready offline until {time}', { time: formatTime(planningMeta.expiresAt) }) }}\n\t\t\t\t</span>\n\t\t\t</div>\n\n\t\t\t<!-- The card links to the queue rather than owning a second copy\n\t\t\t of it: one component, one set of statuses, one set of words. -->\n\t\t\t<CnOfflineQueue v-if=\"showQueue\" :deviceId=\"deviceId\" @requeued=\"loadLocal\" />\n\n\t\t\t<NcLoadingIcon v-if=\"loading\" :size=\"24\" />\n\n\t\t\t<NcEmptyContent v-else-if=\"!activeItem && plannedItems.length === 0\"\n\t\t\t\tdata-testid=\"cn-fi-empty\"\n\t\t\t\t:name=\"t('nextcloud-vue', 'No items planned')\"\n\t\t\t\t:description=\"t('nextcloud-vue', 'Tap “Synchronise day” while online to download your planning.')\" />\n\n\t\t\t<!-- Planning list -->\n\t\t\t<ul v-else-if=\"!activeItem\" class=\"cn-field-inspection__list\" data-testid=\"cn-fi-list\">\n\t\t\t\t<li v-for=\"item in plannedItems\" :key=\"itemId(item)\" class=\"cn-field-inspection__row\">\n\t\t\t\t\t<button type=\"button\"\n\t\t\t\t\t\tclass=\"cn-field-inspection__row-btn\"\n\t\t\t\t\t\t:data-testid=\"`cn-fi-open-${itemId(item)}`\"\n\t\t\t\t\t\t@click=\"openItem(item)\">\n\t\t\t\t\t\t<span class=\"cn-field-inspection__row-title\">{{ itemTitle(item) }}</span>\n\t\t\t\t\t\t<span class=\"cn-field-inspection__row-status\">{{ itemStatus(item) }}</span>\n\t\t\t\t\t</button>\n\t\t\t\t</li>\n\t\t\t</ul>\n\n\t\t\t<!-- Checklist completion for one item -->\n\t\t\t<div v-else class=\"cn-field-inspection__checklist\" data-testid=\"cn-fi-checklist\">\n\t\t\t\t<div class=\"cn-field-inspection__checklist-head\">\n\t\t\t\t\t<NcButton variant=\"tertiary\" data-testid=\"cn-fi-back\" @click=\"closeItem\">\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Back') }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<strong>{{ itemTitle(activeItem) }}</strong>\n\t\t\t\t</div>\n\n\t\t\t\t<p v-if=\"activeTemplate\" class=\"cn-field-inspection__progress\" data-testid=\"cn-fi-progress\">\n\t\t\t\t\t{{ t('nextcloud-vue', '{done} of {total} questions completed', { done: progress.done, total: progress.total }) }}\n\t\t\t\t</p>\n\n\t\t\t\t<form v-if=\"activeTemplate\" @submit.prevent=\"saveChecklist\">\n\t\t\t\t\t<fieldset v-for=\"q in activeTemplate.items\" :key=\"q.questionId\" class=\"cn-field-inspection__question\">\n\t\t\t\t\t\t<legend>\n\t\t\t\t\t\t\t{{ q.text }}<span v-if=\"q.required\" class=\"cn-field-inspection__required\" aria-hidden=\"true\"> *</span>\n\t\t\t\t\t\t</legend>\n\t\t\t\t\t\t<select v-if=\"q.type === 'yes_no'\"\n\t\t\t\t\t\t\tv-model=\"answers[q.questionId].answer\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-fi-answer-${q.questionId}`\"\n\t\t\t\t\t\t\tclass=\"cn-field-inspection__input\">\n\t\t\t\t\t\t\t<option value=\"\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', '— choose —') }}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t<option value=\"yes\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Yes') }}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t<option value=\"no\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'No') }}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t<option value=\"na\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'N/A') }}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t</select>\n\t\t\t\t\t\t<textarea v-else\n\t\t\t\t\t\t\tv-model=\"answers[q.questionId].answer\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-fi-answer-${q.questionId}`\"\n\t\t\t\t\t\t\tclass=\"cn-field-inspection__input\"\n\t\t\t\t\t\t\trows=\"2\" />\n\t\t\t\t\t\t<span v-if=\"errorFor(q.questionId)\" class=\"cn-field-inspection__error\" :data-testid=\"`cn-fi-error-${q.questionId}`\">\n\t\t\t\t\t\t\t{{ errorFor(q.questionId) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</fieldset>\n\n\t\t\t\t\t<!-- `type=\"submit\"` (native HTML type) + `variant=\"primary\"` (style) —\n\t\t\t\t\t @nextcloud/vue 9 split what Vue-2-era code conflated: `type` is now\n\t\t\t\t\t the native button type (`ButtonType`), and the old style-variant\n\t\t\t\t\t prop is `variant`. This line used to pass BOTH wrong: `native-type`\n\t\t\t\t\t (not a real prop — the button silently defaulted to `type=\"button\"`\n\t\t\t\t\t and never fired the form's native submit) and `variant=\"primary\"`\n\t\t\t\t\t (an invalid `ButtonType`, so the visual primary style never applied\n\t\t\t\t\t either). See CnFormPage.vue's inline comment for the submit-button\n\t\t\t\t\t half of this bug in detail. -->\n\t\t\t\t\t<p v-if=\"saveError\"\n\t\t\t\t\t\tclass=\"cn-field-inspection__error\"\n\t\t\t\t\t\trole=\"alert\"\n\t\t\t\t\t\tdata-testid=\"cn-fi-save-error\">\n\t\t\t\t\t\t{{ saveError }}\n\t\t\t\t\t</p>\n\n\t\t\t\t\t<NcButton type=\"submit\"\n\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\tdata-testid=\"cn-fi-save\"\n\t\t\t\t\t\t:disabled=\"saving\">\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Save answers offline') }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</form>\n\n\t\t\t\t<NcEmptyContent v-else\n\t\t\t\t\tdata-testid=\"cn-fi-no-template\"\n\t\t\t\t\t:name=\"t('nextcloud-vue', 'Checklist not available offline')\"\n\t\t\t\t\t:description=\"t('nextcloud-vue', 'Synchronise the day while online to download this checklist.')\" />\n\t\t\t</div>\n\t\t</div>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'\nimport ClipboardCheckOutline from 'vue-material-design-icons/ClipboardCheckOutline.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport CnOfflineQueue from '../../../components/CnOfflineQueue/index.js'\nimport { buildChecklistSubmission } from '../../offline/checklistSubmission.js'\nimport { checklistProgress, classifyGps, normaliseChecklistTemplate, syncIndicator, validateChecklistAnswers } from '../../offline/fieldCollectionHelpers.js'\nimport {\n\tcountPending,\n\tcountStuck,\n\tenqueueMutation,\n\tgetCachedObject,\n\tgetPlannedItems,\n\tgetPlanningMeta,\n\tresolveDeviceId,\n\tstorePlanning,\n} from '../../offline/offlineDb.js'\nimport { fetchPlanning, fetchReferences } from '../../offline/planningFetch.js'\nimport { drainQueue } from '../../offline/syncReplayService.js'\nimport { DEFAULT_FIELD_INSPECTION_CONFIG } from '../field-inspection.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\n\n/**\n * CnFieldInspectionCard — surface-aware offline field data-collection widget.\n *\n * Renders the offline planning list and inline checklist completion across the\n * dashboard / detail surfaces, driven entirely by the generic offline core and\n * the integration's `offlineConfig`.\n */\nexport default {\n\tname: 'CnFieldInspectionCard',\n\n\tcomponents: { CnDetailCard, CnOfflineQueue, NcButton, NcLoadingIcon, NcEmptyContent },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'field-inspection'`). */\n\t\tintegrationId: { type: String, default: 'field-inspection' },\n\t\t/* eslint-disable vue/no-unused-properties -- every integration surface is handed `surface` by its host; this card renders one layout and does not branch on it */\n\t\t/** Rendering surface (AD-19). */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (s) => VALID_SURFACES.includes(s),\n\t\t},\n\t\t/* eslint-enable vue/no-unused-properties */\n\n\t\t/** Object context `{ register, schema, objectId }` forwarded by the host page. */\n\t\tintegrationContext: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** OpenRegister register id (slug or uuid) — falls back to integrationContext. */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid) — falls back to integrationContext. */\n\t\tschema: { type: String, default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Field inspections') },\n\t\t/** Optional Material Design Icon component for the card header. */\n\t\ticon: { type: [Object, Function], default: () => ClipboardCheckOutline },\n\t\t/** Whether the card body is collapsible. */\n\t\tcollapsible: { type: Boolean, default: false },\n\t\t/** Optional explicit assignee uid for the planning query (default: current user). */\n\t\tassignee: { type: String, default: '' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tplannedItems: [],\n\t\t\tplanningMeta: null,\n\t\t\tpendingCount: 0,\n\t\t\t/** Operations that will not send again without somebody acting. */\n\t\t\tstuckCount: 0,\n\t\t\t/** Whether the queue list is open under the card. */\n\t\t\tshowQueue: false,\n\t\t\tloading: true,\n\t\t\tsyncing: false,\n\t\t\tsaving: false,\n\t\t\toffline: typeof navigator !== 'undefined' ? navigator.onLine === false : false,\n\t\t\tdeviceId: resolveDeviceId(),\n\t\t\tactiveItem: null,\n\t\t\tactiveTemplate: null,\n\t\t\tanswers: {},\n\t\t\terrors: [],\n\t\t\t/** Why the last save could not be queued, in words. */\n\t\t\tsaveError: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardTitle() {\n\t\t\treturn this.title || this.integrationId\n\t\t},\n\n\t\tcardIcon() {\n\t\t\treturn this.icon\n\t\t},\n\n\t\t/**\n\t\t * Resolved offline config from the registered descriptor on the shared\n\t\t * registry, falling back to the canonical defaults. A consuming app's\n\t\t * override supplies its own schema names / filter fields here. Read from\n\t\t * the global registry (not a static import) to avoid an import cycle.\n\t\t *\n\t\t * @return {object} The offline config.\n\t\t */\n\t\tconfig() {\n\t\t\tconst registry = (typeof window !== 'undefined'\n\t\t\t\t&& window.OCA && window.OCA.OpenRegister && window.OCA.OpenRegister.integrations) || null\n\t\t\tconst entry = (registry && typeof registry.get === 'function') ? registry.get(this.integrationId) : null\n\t\t\treturn (entry && entry.offlineConfig) || DEFAULT_FIELD_INSPECTION_CONFIG\n\t\t},\n\n\t\t/**\n\t\t * Effective register (prop wins, else integrationContext).\n\t\t *\n\t\t * @return {string} The register id.\n\t\t */\n\t\teffectiveRegister() {\n\t\t\treturn this.register || this.integrationContext.register || ''\n\t\t},\n\n\t\t/**\n\t\t * Effective planned-items schema (config wins, else context/prop schema).\n\t\t *\n\t\t * @return {string} The schema id.\n\t\t */\n\t\teffectiveSchema() {\n\t\t\treturn this.config.plannedSchema || this.schema || this.integrationContext.schema || ''\n\t\t},\n\n\t\t/**\n\t\t * Whether the cached planning has passed its offline lifetime.\n\t\t *\n\t\t * @return {boolean} True when it is past `expiresAt`.\n\t\t */\n\t\tplanningIsStale() {\n\t\t\tconst expiresAt = this.planningMeta?.expiresAt\n\t\t\tif (typeof expiresAt !== 'string' || expiresAt === '') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst expiry = Date.parse(expiresAt)\n\t\t\treturn Number.isNaN(expiry) === false && expiry < Date.now()\n\t\t},\n\n\t\t/**\n\t\t * Where a conflict found during a drain is filed.\n\t\t *\n\t\t * @return {object} The drain config.\n\t\t */\n\t\tdrainConfig() {\n\t\t\treturn {\n\t\t\t\tregister: this.config.register || this.effectiveRegister,\n\t\t\t\tconflictSchema: this.config.conflictSchema || '',\n\t\t\t}\n\t\t},\n\n\t\tindicator() {\n\t\t\treturn syncIndicator(this.pendingCount, this.offline === false, this.stuckCount)\n\t\t},\n\n\t\tprogress() {\n\t\t\treturn checklistProgress(this.activeTemplate, this.answers)\n\t\t},\n\t},\n\n\twatch: {\n\t\tintegrationContext: { immediate: true, handler() {\n\t\t\tthis.loadLocal()\n\t\t} },\n\t},\n\n\t/**\n\t * Wire online/offline listeners (auto-drain on reconnect) + load local state.\n\t *\n\t * @return {Promise<void>}\n\t */\n\tasync mounted() {\n\t\twindow.addEventListener('online', this.onOnline)\n\t\twindow.addEventListener('offline', this.onOffline)\n\t\tawait this.loadLocal()\n\t},\n\n\t/**\n\t * Detach the online/offline listeners.\n\t *\n\t * @return {void}\n\t */\n\tbeforeUnmount() {\n\t\twindow.removeEventListener('online', this.onOnline)\n\t\twindow.removeEventListener('offline', this.onOffline)\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\titemId(item) {\n\t\t\treturn String(item?.id ?? item?.['@self']?.id ?? item?.uuid ?? '')\n\t\t},\n\n\t\titemTitle(item) {\n\t\t\treturn item?.[this.config.titleField] || this.itemId(item)\n\t\t},\n\n\t\titemStatus(item) {\n\t\t\tconst status = item?.status || 'planned'\n\t\t\tconst map = {\n\t\t\t\tplanned: t('nextcloud-vue', 'Planned'),\n\t\t\t\tin_progress: t('nextcloud-vue', 'In progress'),\n\t\t\t\tsynced: t('nextcloud-vue', 'Synced'),\n\t\t\t\tconflict: t('nextcloud-vue', 'Conflict'),\n\t\t\t}\n\t\t\treturn map[status] || status\n\t\t},\n\n\t\tformatTime(iso) {\n\t\t\tif (!iso) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn new Date(iso).toLocaleString()\n\t\t},\n\n\t\terrorFor(questionId) {\n\t\t\tconst e = this.errors.find((x) => x.questionId === questionId)\n\t\t\treturn e ? e.message : ''\n\t\t},\n\n\t\t/**\n\t\t * Load cached planning + pending count from IndexedDB.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadLocal() {\n\t\t\tif (!this.effectiveRegister || !this.effectiveSchema) {\n\t\t\t\tthis.loading = false\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\ttry {\n\t\t\t\tthis.plannedItems = await getPlannedItems(this.effectiveRegister, this.effectiveSchema)\n\t\t\t\tthis.planningMeta = await getPlanningMeta(this.effectiveRegister, this.effectiveSchema)\n\t\t\t\tthis.pendingCount = await countPending(this.deviceId)\n\t\t\t\tthis.stuckCount = await countStuck(this.deviceId)\n\t\t\t} catch (e) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFieldInspectionCard] loadLocal failed', e)\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Download today's planning (planned items + referenced templates) via\n\t\t * the standard OR object API and store it offline.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync syncDay() {\n\t\t\tthis.syncing = true\n\t\t\ttry {\n\t\t\t\tconst cfg = this.config\n\t\t\t\tconst items = await fetchPlanning({\n\t\t\t\t\tregister: this.effectiveRegister,\n\t\t\t\t\tschema: this.effectiveSchema,\n\t\t\t\t\tassigneeField: cfg.assigneeField,\n\t\t\t\t\tassignee: this.assignee || (typeof OC !== 'undefined' && OC.getCurrentUser ? OC.getCurrentUser().uid : ''),\n\t\t\t\t\tdateField: cfg.dateField,\n\t\t\t\t})\n\t\t\t\tconst references = cfg.referenceSchema\n\t\t\t\t\t? await fetchReferences({ register: this.effectiveRegister, referenceSchema: cfg.referenceSchema })\n\t\t\t\t\t: []\n\t\t\t\tawait storePlanning({\n\t\t\t\t\tregister: this.effectiveRegister,\n\t\t\t\t\tschema: this.effectiveSchema,\n\t\t\t\t\titems,\n\t\t\t\t\treferences,\n\t\t\t\t\treferenceSchema: cfg.referenceSchema,\n\t\t\t\t})\n\t\t\t\tawait this.loadLocal()\n\t\t\t} catch (e) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFieldInspectionCard] syncDay failed', e)\n\t\t\t} finally {\n\t\t\t\tthis.syncing = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Open one planned item and load its checklist template from cache.\n\t\t *\n\t\t * @param {object} item The planned item.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync openItem(item) {\n\t\t\tthis.activeItem = item\n\t\t\tthis.errors = []\n\t\t\tconst cfg = this.config\n\t\t\tconst templateRef = item?.[cfg.templateRefField]\n\t\t\t// The app's template shape (sections, its own item keys and type\n\t\t\t// words) is read into the leaf's flat item list here, once.\n\t\t\tthis.activeTemplate = templateRef\n\t\t\t\t? normaliseChecklistTemplate(\n\t\t\t\t\t\tawait getCachedObject(this.effectiveRegister, cfg.referenceSchema, 'references', String(templateRef)),\n\t\t\t\t\t\tcfg,\n\t\t\t\t\t)\n\t\t\t\t: null\n\t\t\tconst initial = {}\n\t\t\tfor (const q of (this.activeTemplate?.items || [])) {\n\t\t\t\tinitial[q.questionId] = { answer: '', evidenceRefs: [] }\n\t\t\t}\n\t\t\tthis.answers = initial\n\t\t},\n\n\t\tcloseItem() {\n\t\t\tthis.activeItem = null\n\t\t\tthis.activeTemplate = null\n\t\t\tthis.answers = {}\n\t\t\tthis.errors = []\n\t\t\tthis.saveError = ''\n\t\t},\n\n\t\t/**\n\t\t * Capture the current GPS fix (tagged on every answer).\n\t\t *\n\t\t * @return {Promise<object|null>} The GPS reading or null.\n\t\t */\n\t\tasync captureGps() {\n\t\t\tif (typeof navigator === 'undefined' || !navigator.geolocation) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn await new Promise((resolve) => {\n\t\t\t\tnavigator.geolocation.getCurrentPosition(\n\t\t\t\t\t(pos) => resolve({ lat: pos.coords.latitude, lon: pos.coords.longitude, accuracy: pos.coords.accuracy }),\n\t\t\t\t\t() => resolve(null),\n\t\t\t\t\t{ timeout: 8000 },\n\t\t\t\t)\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Validate answers, then queue the run: an object create on\n\t\t * `resultSchema`, or, when the config names a `resultEndpoint`, one\n\t\t * `submit` to that endpoint.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync saveChecklist() {\n\t\t\tconst result = validateChecklistAnswers(this.activeTemplate, this.answers)\n\t\t\tthis.errors = result.errors\n\t\t\tif (result.valid === false) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.saving = true\n\t\t\ttry {\n\t\t\t\tconst cfg = this.config\n\t\t\t\tconst gps = await this.captureGps()\n\t\t\t\tconst submission = buildChecklistSubmission(cfg, {\n\t\t\t\t\tplannedItem: this.activeItem,\n\t\t\t\t\ttemplate: this.activeTemplate,\n\t\t\t\t\tanswers: this.answers,\n\t\t\t\t\tgps,\n\t\t\t\t\tgpsSource: classifyGps(gps).source,\n\t\t\t\t\tcapturedAt: new Date().toISOString(),\n\t\t\t\t\tcapturedOffline: this.offline,\n\t\t\t\t})\n\t\t\t\tawait enqueueMutation({\n\t\t\t\t\tdeviceId: this.deviceId,\n\t\t\t\t\toperationType: submission.operationType,\n\t\t\t\t\tregister: this.effectiveRegister,\n\t\t\t\t\tschema: submission.schema,\n\t\t\t\t\tendpoint: submission.endpoint,\n\t\t\t\t\tpayload: submission.payload,\n\t\t\t\t})\n\t\t\t\tthis.pendingCount = await countPending(this.deviceId)\n\t\t\t\tthis.stuckCount = await countStuck(this.deviceId)\n\t\t\t\tthis.closeItem()\n\t\t\t\tif (this.offline === false) {\n\t\t\t\t\tawait this.drain()\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\tthis.saveError = e?.message || t('nextcloud-vue', 'The answers could not be saved on this device.')\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFieldInspectionCard] saveChecklist failed', e)\n\t\t\t} finally {\n\t\t\t\tthis.saving = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Drain the local mutation queue against the OR object API.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync drain() {\n\t\t\tthis.syncing = true\n\t\t\ttry {\n\t\t\t\tawait drainQueue(this.deviceId, this.drainConfig)\n\t\t\t\tthis.pendingCount = await countPending(this.deviceId)\n\t\t\t\tthis.stuckCount = await countStuck(this.deviceId)\n\t\t\t} catch (e) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFieldInspectionCard] drain failed', e)\n\t\t\t} finally {\n\t\t\t\tthis.syncing = false\n\t\t\t}\n\t\t},\n\n\t\tonOnline() {\n\t\t\tthis.offline = false\n\t\t\tthis.drain()\n\t\t},\n\n\t\tonOffline() {\n\t\t\tthis.offline = true\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-field-inspection {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tpadding: 4px 0;\n}\n\n.cn-field-inspection__sync {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.9em;\n}\n\n.cn-field-inspection__dot {\n\twidth: 10px;\n\theight: 10px;\n\tborder-radius: 50%;\n}\n\n.cn-field-inspection__sync--success .cn-field-inspection__dot { background: var(--color-success); }\n\n.cn-field-inspection__sync--warning .cn-field-inspection__dot { background: var(--color-warning); }\n\n.cn-field-inspection__sync--error .cn-field-inspection__dot { background: var(--color-error); }\n\n.cn-field-inspection__actions {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n\tflex-wrap: wrap;\n}\n\n.cn-field-inspection__meta {\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-field-inspection__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n}\n\n.cn-field-inspection__row-btn {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n\twidth: 100%;\n\tpadding: 12px 14px;\n\tmin-height: 44px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large);\n\tbackground: var(--color-main-background);\n\tcursor: pointer;\n\tcolor: var(--color-main-text);\n}\n\n.cn-field-inspection__row-btn:hover {\n\tbackground: var(--color-background-hover);\n}\n\n.cn-field-inspection__row-status {\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-field-inspection__checklist-head {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n}\n\n.cn-field-inspection__progress {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-field-inspection__question {\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tpadding: 12px;\n\tmargin-bottom: 12px;\n}\n\n.cn-field-inspection__input {\n\twidth: 100%;\n\tmin-height: 44px;\n\tmargin-top: 6px;\n}\n\n.cn-field-inspection__required { color: var(--color-error); }\n\n.cn-field-inspection__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.85em;\n\tdisplay: block;\n\tmargin-top: 4px;\n}\n</style>\n"],"names":["CnDetailCard","CnOfflineQueue","t"],"mappings":"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"}
@@ -41,6 +41,12 @@ const _hoisted_15 = { value: "no" };
41
41
  const _hoisted_16 = { value: "na" };
42
42
  const _hoisted_17 = ["onUpdate:modelValue", "data-testid"];
43
43
  const _hoisted_18 = ["data-testid"];
44
+ const _hoisted_19 = {
45
+ key: 0,
46
+ class: "cn-field-inspection__error",
47
+ role: "alert",
48
+ "data-testid": "cn-fi-save-error"
49
+ };
44
50
 
45
51
  function render(_ctx, _cache, $props, $setup, $data, $options) {
46
52
  const _component_NcButton = resolveComponent("NcButton");
@@ -214,6 +220,9 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
214
220
  : createCommentVNode("v-if", true)
215
221
  ]))
216
222
  }), 128 /* KEYED_FRAGMENT */)),
223
+ ($data.saveError)
224
+ ? (openBlock(), createElementBlock("p", _hoisted_19, toDisplayString($data.saveError), 1 /* TEXT */))
225
+ : createCommentVNode("v-if", true),
217
226
  createVNode(_component_NcButton, {
218
227
  type: "submit",
219
228
  variant: "primary",
@@ -1 +1 @@
1
- {"version":3,"file":"CnFieldInspectionCard.vue3.js","sources":["../../../../../src/integrations/builtin/field-inspection/CnFieldInspectionCard.vue?vue&type=template&id=471e815e&lang.js"],"sourcesContent":["<!--\n - SPDX-License-Identifier: EUPL-1.2\n - SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n -\n - CnFieldInspectionCard — surface-aware widget for the `field-inspection`\n - integration leaf (offline field data-collection).\n -\n - Drives the generic offline core (src/integrations/offline): a\n - \"Synchronise day\" action fetches today's planned items via the standard OR\n - object API (planningFetch) into the IndexedDB cache, the planning list lets\n - the user open and complete a checklist offline, and saved answers are queued\n - as OR object mutations that replay on reconnect. The sync-state indicator\n - (online / all-synced / queued / conflict) reflects the pending queue.\n -\n - Generic by construction: the checklist items, the planned-items query and\n - the schema names all come from the integration's `offlineConfig` (resolved\n - from the registry by id) — never from app-specific code in this widget.\n-->\n<template>\n\t<CnDetailCard :title=\"cardTitle\" :icon=\"cardIcon\" :collapsible=\"collapsible\">\n\t\t<div class=\"cn-field-inspection\" data-testid=\"cn-field-inspection-card\">\n\t\t\t<div class=\"cn-field-inspection__sync\" :class=\"`cn-field-inspection__sync--${indicator.tone}`\" data-testid=\"cn-fi-sync-indicator\">\n\t\t\t\t<span class=\"cn-field-inspection__dot\" aria-hidden=\"true\" />\n\t\t\t\t<span>{{ indicator.text }}</span>\n\t\t\t</div>\n\n\t\t\t<div class=\"cn-field-inspection__actions\">\n\t\t\t\t<NcButton variant=\"primary\"\n\t\t\t\t\tdata-testid=\"cn-fi-sync-day\"\n\t\t\t\t\t:disabled=\"syncing || offline\"\n\t\t\t\t\t@click=\"syncDay\">\n\t\t\t\t\t{{ syncing ? t('nextcloud-vue', 'Synchronising…') : t('nextcloud-vue', 'Synchronise day') }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton v-if=\"pendingCount > 0 && !offline\"\n\t\t\t\t\tdata-testid=\"cn-fi-drain-queue\"\n\t\t\t\t\t:disabled=\"syncing\"\n\t\t\t\t\t@click=\"drain\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Sync {n} pending changes', { n: pendingCount }) }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton v-if=\"pendingCount > 0 || stuckCount > 0\"\n\t\t\t\t\tdata-testid=\"cn-fi-open-queue\"\n\t\t\t\t\t@click=\"showQueue = !showQueue\">\n\t\t\t\t\t{{ showQueue\n\t\t\t\t\t\t? t('nextcloud-vue', 'Hide what is waiting')\n\t\t\t\t\t\t: t('nextcloud-vue', 'See what is waiting') }}\n\t\t\t\t</NcButton>\n\t\t\t\t<!-- 🔴 AN EXPIRED PLANNING USED TO READ AS A CURRENT ONE. This\n\t\t\t\t line only ever said \"Ready offline until\", in the past tense\n\t\t\t\t or not, so a planning downloaded three days ago looked the\n\t\t\t\t same as one downloaded an hour ago. In the field there is\n\t\t\t\t nothing else to check it against. -->\n\t\t\t\t<span v-if=\"planningMeta\" class=\"cn-field-inspection__meta\" data-testid=\"cn-fi-planning-meta\">\n\t\t\t\t\t{{ planningIsStale\n\t\t\t\t\t\t? t('nextcloud-vue', 'This planning is out of date. It was downloaded on {time}.', { time: formatTime(planningMeta.syncedAt) })\n\t\t\t\t\t\t: t('nextcloud-vue', 'Ready offline until {time}', { time: formatTime(planningMeta.expiresAt) }) }}\n\t\t\t\t</span>\n\t\t\t</div>\n\n\t\t\t<!-- The card links to the queue rather than owning a second copy\n\t\t\t of it: one component, one set of statuses, one set of words. -->\n\t\t\t<CnOfflineQueue v-if=\"showQueue\" :deviceId=\"deviceId\" @requeued=\"loadLocal\" />\n\n\t\t\t<NcLoadingIcon v-if=\"loading\" :size=\"24\" />\n\n\t\t\t<NcEmptyContent v-else-if=\"!activeItem && plannedItems.length === 0\"\n\t\t\t\tdata-testid=\"cn-fi-empty\"\n\t\t\t\t:name=\"t('nextcloud-vue', 'No items planned')\"\n\t\t\t\t:description=\"t('nextcloud-vue', 'Tap “Synchronise day” while online to download your planning.')\" />\n\n\t\t\t<!-- Planning list -->\n\t\t\t<ul v-else-if=\"!activeItem\" class=\"cn-field-inspection__list\" data-testid=\"cn-fi-list\">\n\t\t\t\t<li v-for=\"item in plannedItems\" :key=\"itemId(item)\" class=\"cn-field-inspection__row\">\n\t\t\t\t\t<button type=\"button\"\n\t\t\t\t\t\tclass=\"cn-field-inspection__row-btn\"\n\t\t\t\t\t\t:data-testid=\"`cn-fi-open-${itemId(item)}`\"\n\t\t\t\t\t\t@click=\"openItem(item)\">\n\t\t\t\t\t\t<span class=\"cn-field-inspection__row-title\">{{ itemTitle(item) }}</span>\n\t\t\t\t\t\t<span class=\"cn-field-inspection__row-status\">{{ itemStatus(item) }}</span>\n\t\t\t\t\t</button>\n\t\t\t\t</li>\n\t\t\t</ul>\n\n\t\t\t<!-- Checklist completion for one item -->\n\t\t\t<div v-else class=\"cn-field-inspection__checklist\" data-testid=\"cn-fi-checklist\">\n\t\t\t\t<div class=\"cn-field-inspection__checklist-head\">\n\t\t\t\t\t<NcButton variant=\"tertiary\" data-testid=\"cn-fi-back\" @click=\"closeItem\">\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Back') }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<strong>{{ itemTitle(activeItem) }}</strong>\n\t\t\t\t</div>\n\n\t\t\t\t<p v-if=\"activeTemplate\" class=\"cn-field-inspection__progress\" data-testid=\"cn-fi-progress\">\n\t\t\t\t\t{{ t('nextcloud-vue', '{done} of {total} questions completed', { done: progress.done, total: progress.total }) }}\n\t\t\t\t</p>\n\n\t\t\t\t<form v-if=\"activeTemplate\" @submit.prevent=\"saveChecklist\">\n\t\t\t\t\t<fieldset v-for=\"q in activeTemplate.items\" :key=\"q.questionId\" class=\"cn-field-inspection__question\">\n\t\t\t\t\t\t<legend>\n\t\t\t\t\t\t\t{{ q.text }}<span v-if=\"q.required\" class=\"cn-field-inspection__required\" aria-hidden=\"true\"> *</span>\n\t\t\t\t\t\t</legend>\n\t\t\t\t\t\t<select v-if=\"q.type === 'yes_no'\"\n\t\t\t\t\t\t\tv-model=\"answers[q.questionId].answer\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-fi-answer-${q.questionId}`\"\n\t\t\t\t\t\t\tclass=\"cn-field-inspection__input\">\n\t\t\t\t\t\t\t<option value=\"\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', '— choose —') }}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t<option value=\"yes\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Yes') }}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t<option value=\"no\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'No') }}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t<option value=\"na\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'N/A') }}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t</select>\n\t\t\t\t\t\t<textarea v-else\n\t\t\t\t\t\t\tv-model=\"answers[q.questionId].answer\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-fi-answer-${q.questionId}`\"\n\t\t\t\t\t\t\tclass=\"cn-field-inspection__input\"\n\t\t\t\t\t\t\trows=\"2\" />\n\t\t\t\t\t\t<span v-if=\"errorFor(q.questionId)\" class=\"cn-field-inspection__error\" :data-testid=\"`cn-fi-error-${q.questionId}`\">\n\t\t\t\t\t\t\t{{ errorFor(q.questionId) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</fieldset>\n\n\t\t\t\t\t<!-- `type=\"submit\"` (native HTML type) + `variant=\"primary\"` (style) —\n\t\t\t\t\t @nextcloud/vue 9 split what Vue-2-era code conflated: `type` is now\n\t\t\t\t\t the native button type (`ButtonType`), and the old style-variant\n\t\t\t\t\t prop is `variant`. This line used to pass BOTH wrong: `native-type`\n\t\t\t\t\t (not a real prop — the button silently defaulted to `type=\"button\"`\n\t\t\t\t\t and never fired the form's native submit) and `variant=\"primary\"`\n\t\t\t\t\t (an invalid `ButtonType`, so the visual primary style never applied\n\t\t\t\t\t either). See CnFormPage.vue's inline comment for the submit-button\n\t\t\t\t\t half of this bug in detail. -->\n\t\t\t\t\t<NcButton type=\"submit\"\n\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\tdata-testid=\"cn-fi-save\"\n\t\t\t\t\t\t:disabled=\"saving\">\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Save answers offline') }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</form>\n\n\t\t\t\t<NcEmptyContent v-else\n\t\t\t\t\tdata-testid=\"cn-fi-no-template\"\n\t\t\t\t\t:name=\"t('nextcloud-vue', 'Checklist not available offline')\"\n\t\t\t\t\t:description=\"t('nextcloud-vue', 'Synchronise the day while online to download this checklist.')\" />\n\t\t\t</div>\n\t\t</div>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'\nimport ClipboardCheckOutline from 'vue-material-design-icons/ClipboardCheckOutline.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport CnOfflineQueue from '../../../components/CnOfflineQueue/index.js'\nimport { checklistProgress, classifyGps, syncIndicator, validateChecklistAnswers } from '../../offline/fieldCollectionHelpers.js'\nimport {\n\tcountPending,\n\tcountStuck,\n\tenqueueMutation,\n\tgetCachedObject,\n\tgetPlannedItems,\n\tgetPlanningMeta,\n\tresolveDeviceId,\n\tstorePlanning,\n} from '../../offline/offlineDb.js'\nimport { fetchPlanning, fetchReferences } from '../../offline/planningFetch.js'\nimport { drainQueue } from '../../offline/syncReplayService.js'\nimport { DEFAULT_FIELD_INSPECTION_CONFIG } from '../field-inspection.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\n\n/**\n * CnFieldInspectionCard — surface-aware offline field data-collection widget.\n *\n * Renders the offline planning list and inline checklist completion across the\n * dashboard / detail surfaces, driven entirely by the generic offline core and\n * the integration's `offlineConfig`.\n */\nexport default {\n\tname: 'CnFieldInspectionCard',\n\n\tcomponents: { CnDetailCard, CnOfflineQueue, NcButton, NcLoadingIcon, NcEmptyContent },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'field-inspection'`). */\n\t\tintegrationId: { type: String, default: 'field-inspection' },\n\t\t/* eslint-disable vue/no-unused-properties -- every integration surface is handed `surface` by its host; this card renders one layout and does not branch on it */\n\t\t/** Rendering surface (AD-19). */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (s) => VALID_SURFACES.includes(s),\n\t\t},\n\t\t/* eslint-enable vue/no-unused-properties */\n\n\t\t/** Object context `{ register, schema, objectId }` forwarded by the host page. */\n\t\tintegrationContext: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** OpenRegister register id (slug or uuid) — falls back to integrationContext. */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid) — falls back to integrationContext. */\n\t\tschema: { type: String, default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Field inspections') },\n\t\t/** Optional Material Design Icon component for the card header. */\n\t\ticon: { type: [Object, Function], default: () => ClipboardCheckOutline },\n\t\t/** Whether the card body is collapsible. */\n\t\tcollapsible: { type: Boolean, default: false },\n\t\t/** Optional explicit assignee uid for the planning query (default: current user). */\n\t\tassignee: { type: String, default: '' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tplannedItems: [],\n\t\t\tplanningMeta: null,\n\t\t\tpendingCount: 0,\n\t\t\t/** Operations that will not send again without somebody acting. */\n\t\t\tstuckCount: 0,\n\t\t\t/** Whether the queue list is open under the card. */\n\t\t\tshowQueue: false,\n\t\t\tloading: true,\n\t\t\tsyncing: false,\n\t\t\tsaving: false,\n\t\t\toffline: typeof navigator !== 'undefined' ? navigator.onLine === false : false,\n\t\t\tdeviceId: resolveDeviceId(),\n\t\t\tactiveItem: null,\n\t\t\tactiveTemplate: null,\n\t\t\tanswers: {},\n\t\t\terrors: [],\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardTitle() {\n\t\t\treturn this.title || this.integrationId\n\t\t},\n\n\t\tcardIcon() {\n\t\t\treturn this.icon\n\t\t},\n\n\t\t/**\n\t\t * Resolved offline config from the registered descriptor on the shared\n\t\t * registry, falling back to the canonical defaults. A consuming app's\n\t\t * override supplies its own schema names / filter fields here. Read from\n\t\t * the global registry (not a static import) to avoid an import cycle.\n\t\t *\n\t\t * @return {object} The offline config.\n\t\t */\n\t\tconfig() {\n\t\t\tconst registry = (typeof window !== 'undefined'\n\t\t\t\t&& window.OCA && window.OCA.OpenRegister && window.OCA.OpenRegister.integrations) || null\n\t\t\tconst entry = (registry && typeof registry.get === 'function') ? registry.get(this.integrationId) : null\n\t\t\treturn (entry && entry.offlineConfig) || DEFAULT_FIELD_INSPECTION_CONFIG\n\t\t},\n\n\t\t/**\n\t\t * Effective register (prop wins, else integrationContext).\n\t\t *\n\t\t * @return {string} The register id.\n\t\t */\n\t\teffectiveRegister() {\n\t\t\treturn this.register || this.integrationContext.register || ''\n\t\t},\n\n\t\t/**\n\t\t * Effective planned-items schema (config wins, else context/prop schema).\n\t\t *\n\t\t * @return {string} The schema id.\n\t\t */\n\t\teffectiveSchema() {\n\t\t\treturn this.config.plannedSchema || this.schema || this.integrationContext.schema || ''\n\t\t},\n\n\t\t/**\n\t\t * Whether the cached planning has passed its offline lifetime.\n\t\t *\n\t\t * @return {boolean} True when it is past `expiresAt`.\n\t\t */\n\t\tplanningIsStale() {\n\t\t\tconst expiresAt = this.planningMeta?.expiresAt\n\t\t\tif (typeof expiresAt !== 'string' || expiresAt === '') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst expiry = Date.parse(expiresAt)\n\t\t\treturn Number.isNaN(expiry) === false && expiry < Date.now()\n\t\t},\n\n\t\t/**\n\t\t * Where a conflict found during a drain is filed.\n\t\t *\n\t\t * @return {object} The drain config.\n\t\t */\n\t\tdrainConfig() {\n\t\t\treturn {\n\t\t\t\tregister: this.config.register || this.effectiveRegister,\n\t\t\t\tconflictSchema: this.config.conflictSchema || '',\n\t\t\t}\n\t\t},\n\n\t\tindicator() {\n\t\t\treturn syncIndicator(this.pendingCount, this.offline === false, this.stuckCount)\n\t\t},\n\n\t\tprogress() {\n\t\t\treturn checklistProgress(this.activeTemplate, this.answers)\n\t\t},\n\t},\n\n\twatch: {\n\t\tintegrationContext: { immediate: true, handler() {\n\t\t\tthis.loadLocal()\n\t\t} },\n\t},\n\n\t/**\n\t * Wire online/offline listeners (auto-drain on reconnect) + load local state.\n\t *\n\t * @return {Promise<void>}\n\t */\n\tasync mounted() {\n\t\twindow.addEventListener('online', this.onOnline)\n\t\twindow.addEventListener('offline', this.onOffline)\n\t\tawait this.loadLocal()\n\t},\n\n\t/**\n\t * Detach the online/offline listeners.\n\t *\n\t * @return {void}\n\t */\n\tbeforeUnmount() {\n\t\twindow.removeEventListener('online', this.onOnline)\n\t\twindow.removeEventListener('offline', this.onOffline)\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\titemId(item) {\n\t\t\treturn String(item?.id ?? item?.['@self']?.id ?? item?.uuid ?? '')\n\t\t},\n\n\t\titemTitle(item) {\n\t\t\treturn item?.[this.config.titleField] || this.itemId(item)\n\t\t},\n\n\t\titemStatus(item) {\n\t\t\tconst status = item?.status || 'planned'\n\t\t\tconst map = {\n\t\t\t\tplanned: t('nextcloud-vue', 'Planned'),\n\t\t\t\tin_progress: t('nextcloud-vue', 'In progress'),\n\t\t\t\tsynced: t('nextcloud-vue', 'Synced'),\n\t\t\t\tconflict: t('nextcloud-vue', 'Conflict'),\n\t\t\t}\n\t\t\treturn map[status] || status\n\t\t},\n\n\t\tformatTime(iso) {\n\t\t\tif (!iso) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn new Date(iso).toLocaleString()\n\t\t},\n\n\t\terrorFor(questionId) {\n\t\t\tconst e = this.errors.find((x) => x.questionId === questionId)\n\t\t\treturn e ? e.message : ''\n\t\t},\n\n\t\t/**\n\t\t * Load cached planning + pending count from IndexedDB.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadLocal() {\n\t\t\tif (!this.effectiveRegister || !this.effectiveSchema) {\n\t\t\t\tthis.loading = false\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\ttry {\n\t\t\t\tthis.plannedItems = await getPlannedItems(this.effectiveRegister, this.effectiveSchema)\n\t\t\t\tthis.planningMeta = await getPlanningMeta(this.effectiveRegister, this.effectiveSchema)\n\t\t\t\tthis.pendingCount = await countPending(this.deviceId)\n\t\t\t\tthis.stuckCount = await countStuck(this.deviceId)\n\t\t\t} catch (e) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFieldInspectionCard] loadLocal failed', e)\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Download today's planning (planned items + referenced templates) via\n\t\t * the standard OR object API and store it offline.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync syncDay() {\n\t\t\tthis.syncing = true\n\t\t\ttry {\n\t\t\t\tconst cfg = this.config\n\t\t\t\tconst items = await fetchPlanning({\n\t\t\t\t\tregister: this.effectiveRegister,\n\t\t\t\t\tschema: this.effectiveSchema,\n\t\t\t\t\tassigneeField: cfg.assigneeField,\n\t\t\t\t\tassignee: this.assignee || (typeof OC !== 'undefined' && OC.getCurrentUser ? OC.getCurrentUser().uid : ''),\n\t\t\t\t\tdateField: cfg.dateField,\n\t\t\t\t})\n\t\t\t\tconst references = cfg.referenceSchema\n\t\t\t\t\t? await fetchReferences({ register: this.effectiveRegister, referenceSchema: cfg.referenceSchema })\n\t\t\t\t\t: []\n\t\t\t\tawait storePlanning({\n\t\t\t\t\tregister: this.effectiveRegister,\n\t\t\t\t\tschema: this.effectiveSchema,\n\t\t\t\t\titems,\n\t\t\t\t\treferences,\n\t\t\t\t\treferenceSchema: cfg.referenceSchema,\n\t\t\t\t})\n\t\t\t\tawait this.loadLocal()\n\t\t\t} catch (e) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFieldInspectionCard] syncDay failed', e)\n\t\t\t} finally {\n\t\t\t\tthis.syncing = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Open one planned item and load its checklist template from cache.\n\t\t *\n\t\t * @param {object} item The planned item.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync openItem(item) {\n\t\t\tthis.activeItem = item\n\t\t\tthis.errors = []\n\t\t\tconst cfg = this.config\n\t\t\tconst templateRef = item?.[cfg.templateRefField]\n\t\t\tthis.activeTemplate = templateRef\n\t\t\t\t? await getCachedObject(this.effectiveRegister, cfg.referenceSchema, 'references', String(templateRef))\n\t\t\t\t: null\n\t\t\tconst initial = {}\n\t\t\tfor (const q of (this.activeTemplate?.items || [])) {\n\t\t\t\tinitial[q.questionId] = { answer: '', evidenceRefs: [] }\n\t\t\t}\n\t\t\tthis.answers = initial\n\t\t},\n\n\t\tcloseItem() {\n\t\t\tthis.activeItem = null\n\t\t\tthis.activeTemplate = null\n\t\t\tthis.answers = {}\n\t\t\tthis.errors = []\n\t\t},\n\n\t\t/**\n\t\t * Capture the current GPS fix (tagged on every answer).\n\t\t *\n\t\t * @return {Promise<object|null>} The GPS reading or null.\n\t\t */\n\t\tasync captureGps() {\n\t\t\tif (typeof navigator === 'undefined' || !navigator.geolocation) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn await new Promise((resolve) => {\n\t\t\t\tnavigator.geolocation.getCurrentPosition(\n\t\t\t\t\t(pos) => resolve({ lat: pos.coords.latitude, lon: pos.coords.longitude, accuracy: pos.coords.accuracy }),\n\t\t\t\t\t() => resolve(null),\n\t\t\t\t\t{ timeout: 8000 },\n\t\t\t\t)\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Validate answers, then queue a `create` mutation for the result.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync saveChecklist() {\n\t\t\tconst result = validateChecklistAnswers(this.activeTemplate, this.answers)\n\t\t\tthis.errors = result.errors\n\t\t\tif (result.valid === false) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.saving = true\n\t\t\ttry {\n\t\t\t\tconst cfg = this.config\n\t\t\t\tconst gps = await this.captureGps()\n\t\t\t\tconst gpsClass = classifyGps(gps)\n\t\t\t\tconst now = new Date().toISOString()\n\t\t\t\tconst items = this.activeTemplate.items.map((q) => ({\n\t\t\t\t\tquestionId: q.questionId,\n\t\t\t\t\tanswer: this.answers[q.questionId].answer,\n\t\t\t\t\tevidenceRefs: this.answers[q.questionId].evidenceRefs,\n\t\t\t\t\tansweredAt: now,\n\t\t\t\t\tgpsAtAnswer: gps ? { ...gps, source: gpsClass.source } : { source: 'sensorless' },\n\t\t\t\t}))\n\t\t\t\tconst payload = {\n\t\t\t\t\tinspectionRef: this.itemId(this.activeItem),\n\t\t\t\t\tchecklistTemplateRef: this.activeTemplate.id ?? this.activeTemplate['@self']?.id,\n\t\t\t\t\titems,\n\t\t\t\t}\n\t\t\t\tawait enqueueMutation({\n\t\t\t\t\tdeviceId: this.deviceId,\n\t\t\t\t\toperationType: 'create',\n\t\t\t\t\tregister: this.effectiveRegister,\n\t\t\t\t\tschema: cfg.resultSchema,\n\t\t\t\t\tpayload,\n\t\t\t\t})\n\t\t\t\tthis.pendingCount = await countPending(this.deviceId)\n\t\t\t\tthis.stuckCount = await countStuck(this.deviceId)\n\t\t\t\tthis.closeItem()\n\t\t\t\tif (this.offline === false) {\n\t\t\t\t\tawait this.drain()\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFieldInspectionCard] saveChecklist failed', e)\n\t\t\t} finally {\n\t\t\t\tthis.saving = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Drain the local mutation queue against the OR object API.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync drain() {\n\t\t\tthis.syncing = true\n\t\t\ttry {\n\t\t\t\tawait drainQueue(this.deviceId, this.drainConfig)\n\t\t\t\tthis.pendingCount = await countPending(this.deviceId)\n\t\t\t\tthis.stuckCount = await countStuck(this.deviceId)\n\t\t\t} catch (e) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFieldInspectionCard] drain failed', e)\n\t\t\t} finally {\n\t\t\t\tthis.syncing = false\n\t\t\t}\n\t\t},\n\n\t\tonOnline() {\n\t\t\tthis.offline = false\n\t\t\tthis.drain()\n\t\t},\n\n\t\tonOffline() {\n\t\t\tthis.offline = true\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-field-inspection {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tpadding: 4px 0;\n}\n\n.cn-field-inspection__sync {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.9em;\n}\n\n.cn-field-inspection__dot {\n\twidth: 10px;\n\theight: 10px;\n\tborder-radius: 50%;\n}\n\n.cn-field-inspection__sync--success .cn-field-inspection__dot { background: var(--color-success); }\n\n.cn-field-inspection__sync--warning .cn-field-inspection__dot { background: var(--color-warning); }\n\n.cn-field-inspection__sync--error .cn-field-inspection__dot { background: var(--color-error); }\n\n.cn-field-inspection__actions {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n\tflex-wrap: wrap;\n}\n\n.cn-field-inspection__meta {\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-field-inspection__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n}\n\n.cn-field-inspection__row-btn {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n\twidth: 100%;\n\tpadding: 12px 14px;\n\tmin-height: 44px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large);\n\tbackground: var(--color-main-background);\n\tcursor: pointer;\n\tcolor: var(--color-main-text);\n}\n\n.cn-field-inspection__row-btn:hover {\n\tbackground: var(--color-background-hover);\n}\n\n.cn-field-inspection__row-status {\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-field-inspection__checklist-head {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n}\n\n.cn-field-inspection__progress {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-field-inspection__question {\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tpadding: 12px;\n\tmargin-bottom: 12px;\n}\n\n.cn-field-inspection__input {\n\twidth: 100%;\n\tmin-height: 44px;\n\tmargin-top: 6px;\n}\n\n.cn-field-inspection__required { color: var(--color-error); }\n\n.cn-field-inspection__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.85em;\n\tdisplay: block;\n\tmargin-top: 4px;\n}\n</style>\n"],"names":["_createBlock","_createElementVNode","_normalizeClass","_toDisplayString","_createVNode","_createTextVNode","_createElementBlock","_openBlock","_Fragment","_renderList","_vModelSelect","_vModelText"],"mappings":"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"}
1
+ {"version":3,"file":"CnFieldInspectionCard.vue3.js","sources":["../../../../../src/integrations/builtin/field-inspection/CnFieldInspectionCard.vue?vue&type=template&id=471e815e&lang.js"],"sourcesContent":["<!--\n - SPDX-License-Identifier: EUPL-1.2\n - SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n -\n - CnFieldInspectionCard — surface-aware widget for the `field-inspection`\n - integration leaf (offline field data-collection).\n -\n - Drives the generic offline core (src/integrations/offline): a\n - \"Synchronise day\" action fetches today's planned items via the standard OR\n - object API (planningFetch) into the IndexedDB cache, the planning list lets\n - the user open and complete a checklist offline, and saved answers are queued\n - as OR object mutations that replay on reconnect. The sync-state indicator\n - (online / all-synced / queued / conflict) reflects the pending queue.\n -\n - Generic by construction: the checklist items, the planned-items query and\n - the schema names all come from the integration's `offlineConfig` (resolved\n - from the registry by id) — never from app-specific code in this widget.\n-->\n<template>\n\t<CnDetailCard :title=\"cardTitle\" :icon=\"cardIcon\" :collapsible=\"collapsible\">\n\t\t<div class=\"cn-field-inspection\" data-testid=\"cn-field-inspection-card\">\n\t\t\t<div class=\"cn-field-inspection__sync\" :class=\"`cn-field-inspection__sync--${indicator.tone}`\" data-testid=\"cn-fi-sync-indicator\">\n\t\t\t\t<span class=\"cn-field-inspection__dot\" aria-hidden=\"true\" />\n\t\t\t\t<span>{{ indicator.text }}</span>\n\t\t\t</div>\n\n\t\t\t<div class=\"cn-field-inspection__actions\">\n\t\t\t\t<NcButton variant=\"primary\"\n\t\t\t\t\tdata-testid=\"cn-fi-sync-day\"\n\t\t\t\t\t:disabled=\"syncing || offline\"\n\t\t\t\t\t@click=\"syncDay\">\n\t\t\t\t\t{{ syncing ? t('nextcloud-vue', 'Synchronising…') : t('nextcloud-vue', 'Synchronise day') }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton v-if=\"pendingCount > 0 && !offline\"\n\t\t\t\t\tdata-testid=\"cn-fi-drain-queue\"\n\t\t\t\t\t:disabled=\"syncing\"\n\t\t\t\t\t@click=\"drain\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Sync {n} pending changes', { n: pendingCount }) }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton v-if=\"pendingCount > 0 || stuckCount > 0\"\n\t\t\t\t\tdata-testid=\"cn-fi-open-queue\"\n\t\t\t\t\t@click=\"showQueue = !showQueue\">\n\t\t\t\t\t{{ showQueue\n\t\t\t\t\t\t? t('nextcloud-vue', 'Hide what is waiting')\n\t\t\t\t\t\t: t('nextcloud-vue', 'See what is waiting') }}\n\t\t\t\t</NcButton>\n\t\t\t\t<!-- 🔴 AN EXPIRED PLANNING USED TO READ AS A CURRENT ONE. This\n\t\t\t\t line only ever said \"Ready offline until\", in the past tense\n\t\t\t\t or not, so a planning downloaded three days ago looked the\n\t\t\t\t same as one downloaded an hour ago. In the field there is\n\t\t\t\t nothing else to check it against. -->\n\t\t\t\t<span v-if=\"planningMeta\" class=\"cn-field-inspection__meta\" data-testid=\"cn-fi-planning-meta\">\n\t\t\t\t\t{{ planningIsStale\n\t\t\t\t\t\t? t('nextcloud-vue', 'This planning is out of date. It was downloaded on {time}.', { time: formatTime(planningMeta.syncedAt) })\n\t\t\t\t\t\t: t('nextcloud-vue', 'Ready offline until {time}', { time: formatTime(planningMeta.expiresAt) }) }}\n\t\t\t\t</span>\n\t\t\t</div>\n\n\t\t\t<!-- The card links to the queue rather than owning a second copy\n\t\t\t of it: one component, one set of statuses, one set of words. -->\n\t\t\t<CnOfflineQueue v-if=\"showQueue\" :deviceId=\"deviceId\" @requeued=\"loadLocal\" />\n\n\t\t\t<NcLoadingIcon v-if=\"loading\" :size=\"24\" />\n\n\t\t\t<NcEmptyContent v-else-if=\"!activeItem && plannedItems.length === 0\"\n\t\t\t\tdata-testid=\"cn-fi-empty\"\n\t\t\t\t:name=\"t('nextcloud-vue', 'No items planned')\"\n\t\t\t\t:description=\"t('nextcloud-vue', 'Tap “Synchronise day” while online to download your planning.')\" />\n\n\t\t\t<!-- Planning list -->\n\t\t\t<ul v-else-if=\"!activeItem\" class=\"cn-field-inspection__list\" data-testid=\"cn-fi-list\">\n\t\t\t\t<li v-for=\"item in plannedItems\" :key=\"itemId(item)\" class=\"cn-field-inspection__row\">\n\t\t\t\t\t<button type=\"button\"\n\t\t\t\t\t\tclass=\"cn-field-inspection__row-btn\"\n\t\t\t\t\t\t:data-testid=\"`cn-fi-open-${itemId(item)}`\"\n\t\t\t\t\t\t@click=\"openItem(item)\">\n\t\t\t\t\t\t<span class=\"cn-field-inspection__row-title\">{{ itemTitle(item) }}</span>\n\t\t\t\t\t\t<span class=\"cn-field-inspection__row-status\">{{ itemStatus(item) }}</span>\n\t\t\t\t\t</button>\n\t\t\t\t</li>\n\t\t\t</ul>\n\n\t\t\t<!-- Checklist completion for one item -->\n\t\t\t<div v-else class=\"cn-field-inspection__checklist\" data-testid=\"cn-fi-checklist\">\n\t\t\t\t<div class=\"cn-field-inspection__checklist-head\">\n\t\t\t\t\t<NcButton variant=\"tertiary\" data-testid=\"cn-fi-back\" @click=\"closeItem\">\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Back') }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<strong>{{ itemTitle(activeItem) }}</strong>\n\t\t\t\t</div>\n\n\t\t\t\t<p v-if=\"activeTemplate\" class=\"cn-field-inspection__progress\" data-testid=\"cn-fi-progress\">\n\t\t\t\t\t{{ t('nextcloud-vue', '{done} of {total} questions completed', { done: progress.done, total: progress.total }) }}\n\t\t\t\t</p>\n\n\t\t\t\t<form v-if=\"activeTemplate\" @submit.prevent=\"saveChecklist\">\n\t\t\t\t\t<fieldset v-for=\"q in activeTemplate.items\" :key=\"q.questionId\" class=\"cn-field-inspection__question\">\n\t\t\t\t\t\t<legend>\n\t\t\t\t\t\t\t{{ q.text }}<span v-if=\"q.required\" class=\"cn-field-inspection__required\" aria-hidden=\"true\"> *</span>\n\t\t\t\t\t\t</legend>\n\t\t\t\t\t\t<select v-if=\"q.type === 'yes_no'\"\n\t\t\t\t\t\t\tv-model=\"answers[q.questionId].answer\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-fi-answer-${q.questionId}`\"\n\t\t\t\t\t\t\tclass=\"cn-field-inspection__input\">\n\t\t\t\t\t\t\t<option value=\"\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', '— choose —') }}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t<option value=\"yes\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Yes') }}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t<option value=\"no\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'No') }}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t<option value=\"na\">\n\t\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'N/A') }}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t</select>\n\t\t\t\t\t\t<textarea v-else\n\t\t\t\t\t\t\tv-model=\"answers[q.questionId].answer\"\n\t\t\t\t\t\t\t:data-testid=\"`cn-fi-answer-${q.questionId}`\"\n\t\t\t\t\t\t\tclass=\"cn-field-inspection__input\"\n\t\t\t\t\t\t\trows=\"2\" />\n\t\t\t\t\t\t<span v-if=\"errorFor(q.questionId)\" class=\"cn-field-inspection__error\" :data-testid=\"`cn-fi-error-${q.questionId}`\">\n\t\t\t\t\t\t\t{{ errorFor(q.questionId) }}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</fieldset>\n\n\t\t\t\t\t<!-- `type=\"submit\"` (native HTML type) + `variant=\"primary\"` (style) —\n\t\t\t\t\t @nextcloud/vue 9 split what Vue-2-era code conflated: `type` is now\n\t\t\t\t\t the native button type (`ButtonType`), and the old style-variant\n\t\t\t\t\t prop is `variant`. This line used to pass BOTH wrong: `native-type`\n\t\t\t\t\t (not a real prop — the button silently defaulted to `type=\"button\"`\n\t\t\t\t\t and never fired the form's native submit) and `variant=\"primary\"`\n\t\t\t\t\t (an invalid `ButtonType`, so the visual primary style never applied\n\t\t\t\t\t either). See CnFormPage.vue's inline comment for the submit-button\n\t\t\t\t\t half of this bug in detail. -->\n\t\t\t\t\t<p v-if=\"saveError\"\n\t\t\t\t\t\tclass=\"cn-field-inspection__error\"\n\t\t\t\t\t\trole=\"alert\"\n\t\t\t\t\t\tdata-testid=\"cn-fi-save-error\">\n\t\t\t\t\t\t{{ saveError }}\n\t\t\t\t\t</p>\n\n\t\t\t\t\t<NcButton type=\"submit\"\n\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\tdata-testid=\"cn-fi-save\"\n\t\t\t\t\t\t:disabled=\"saving\">\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Save answers offline') }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</form>\n\n\t\t\t\t<NcEmptyContent v-else\n\t\t\t\t\tdata-testid=\"cn-fi-no-template\"\n\t\t\t\t\t:name=\"t('nextcloud-vue', 'Checklist not available offline')\"\n\t\t\t\t\t:description=\"t('nextcloud-vue', 'Synchronise the day while online to download this checklist.')\" />\n\t\t\t</div>\n\t\t</div>\n\t</CnDetailCard>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'\nimport ClipboardCheckOutline from 'vue-material-design-icons/ClipboardCheckOutline.vue'\nimport CnDetailCard from '../../../components/CnDetailCard/CnDetailCard.vue'\nimport CnOfflineQueue from '../../../components/CnOfflineQueue/index.js'\nimport { buildChecklistSubmission } from '../../offline/checklistSubmission.js'\nimport { checklistProgress, classifyGps, normaliseChecklistTemplate, syncIndicator, validateChecklistAnswers } from '../../offline/fieldCollectionHelpers.js'\nimport {\n\tcountPending,\n\tcountStuck,\n\tenqueueMutation,\n\tgetCachedObject,\n\tgetPlannedItems,\n\tgetPlanningMeta,\n\tresolveDeviceId,\n\tstorePlanning,\n} from '../../offline/offlineDb.js'\nimport { fetchPlanning, fetchReferences } from '../../offline/planningFetch.js'\nimport { drainQueue } from '../../offline/syncReplayService.js'\nimport { DEFAULT_FIELD_INSPECTION_CONFIG } from '../field-inspection.js'\n\nconst VALID_SURFACES = ['user-dashboard', 'app-dashboard', 'detail-page', 'single-entity']\n\n/**\n * CnFieldInspectionCard — surface-aware offline field data-collection widget.\n *\n * Renders the offline planning list and inline checklist completion across the\n * dashboard / detail surfaces, driven entirely by the generic offline core and\n * the integration's `offlineConfig`.\n */\nexport default {\n\tname: 'CnFieldInspectionCard',\n\n\tcomponents: { CnDetailCard, CnOfflineQueue, NcButton, NcLoadingIcon, NcEmptyContent },\n\n\tprops: {\n\t\t/** Stable integration id (forwarded from the registry — always `'field-inspection'`). */\n\t\tintegrationId: { type: String, default: 'field-inspection' },\n\t\t/* eslint-disable vue/no-unused-properties -- every integration surface is handed `surface` by its host; this card renders one layout and does not branch on it */\n\t\t/** Rendering surface (AD-19). */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t\tvalidator: (s) => VALID_SURFACES.includes(s),\n\t\t},\n\t\t/* eslint-enable vue/no-unused-properties */\n\n\t\t/** Object context `{ register, schema, objectId }` forwarded by the host page. */\n\t\tintegrationContext: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** OpenRegister register id (slug or uuid) — falls back to integrationContext. */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid) — falls back to integrationContext. */\n\t\tschema: { type: String, default: '' },\n\t\t/** Pre-translated card title. */\n\t\ttitle: { type: String, default: () => t('nextcloud-vue', 'Field inspections') },\n\t\t/** Optional Material Design Icon component for the card header. */\n\t\ticon: { type: [Object, Function], default: () => ClipboardCheckOutline },\n\t\t/** Whether the card body is collapsible. */\n\t\tcollapsible: { type: Boolean, default: false },\n\t\t/** Optional explicit assignee uid for the planning query (default: current user). */\n\t\tassignee: { type: String, default: '' },\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tplannedItems: [],\n\t\t\tplanningMeta: null,\n\t\t\tpendingCount: 0,\n\t\t\t/** Operations that will not send again without somebody acting. */\n\t\t\tstuckCount: 0,\n\t\t\t/** Whether the queue list is open under the card. */\n\t\t\tshowQueue: false,\n\t\t\tloading: true,\n\t\t\tsyncing: false,\n\t\t\tsaving: false,\n\t\t\toffline: typeof navigator !== 'undefined' ? navigator.onLine === false : false,\n\t\t\tdeviceId: resolveDeviceId(),\n\t\t\tactiveItem: null,\n\t\t\tactiveTemplate: null,\n\t\t\tanswers: {},\n\t\t\terrors: [],\n\t\t\t/** Why the last save could not be queued, in words. */\n\t\t\tsaveError: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tcardTitle() {\n\t\t\treturn this.title || this.integrationId\n\t\t},\n\n\t\tcardIcon() {\n\t\t\treturn this.icon\n\t\t},\n\n\t\t/**\n\t\t * Resolved offline config from the registered descriptor on the shared\n\t\t * registry, falling back to the canonical defaults. A consuming app's\n\t\t * override supplies its own schema names / filter fields here. Read from\n\t\t * the global registry (not a static import) to avoid an import cycle.\n\t\t *\n\t\t * @return {object} The offline config.\n\t\t */\n\t\tconfig() {\n\t\t\tconst registry = (typeof window !== 'undefined'\n\t\t\t\t&& window.OCA && window.OCA.OpenRegister && window.OCA.OpenRegister.integrations) || null\n\t\t\tconst entry = (registry && typeof registry.get === 'function') ? registry.get(this.integrationId) : null\n\t\t\treturn (entry && entry.offlineConfig) || DEFAULT_FIELD_INSPECTION_CONFIG\n\t\t},\n\n\t\t/**\n\t\t * Effective register (prop wins, else integrationContext).\n\t\t *\n\t\t * @return {string} The register id.\n\t\t */\n\t\teffectiveRegister() {\n\t\t\treturn this.register || this.integrationContext.register || ''\n\t\t},\n\n\t\t/**\n\t\t * Effective planned-items schema (config wins, else context/prop schema).\n\t\t *\n\t\t * @return {string} The schema id.\n\t\t */\n\t\teffectiveSchema() {\n\t\t\treturn this.config.plannedSchema || this.schema || this.integrationContext.schema || ''\n\t\t},\n\n\t\t/**\n\t\t * Whether the cached planning has passed its offline lifetime.\n\t\t *\n\t\t * @return {boolean} True when it is past `expiresAt`.\n\t\t */\n\t\tplanningIsStale() {\n\t\t\tconst expiresAt = this.planningMeta?.expiresAt\n\t\t\tif (typeof expiresAt !== 'string' || expiresAt === '') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst expiry = Date.parse(expiresAt)\n\t\t\treturn Number.isNaN(expiry) === false && expiry < Date.now()\n\t\t},\n\n\t\t/**\n\t\t * Where a conflict found during a drain is filed.\n\t\t *\n\t\t * @return {object} The drain config.\n\t\t */\n\t\tdrainConfig() {\n\t\t\treturn {\n\t\t\t\tregister: this.config.register || this.effectiveRegister,\n\t\t\t\tconflictSchema: this.config.conflictSchema || '',\n\t\t\t}\n\t\t},\n\n\t\tindicator() {\n\t\t\treturn syncIndicator(this.pendingCount, this.offline === false, this.stuckCount)\n\t\t},\n\n\t\tprogress() {\n\t\t\treturn checklistProgress(this.activeTemplate, this.answers)\n\t\t},\n\t},\n\n\twatch: {\n\t\tintegrationContext: { immediate: true, handler() {\n\t\t\tthis.loadLocal()\n\t\t} },\n\t},\n\n\t/**\n\t * Wire online/offline listeners (auto-drain on reconnect) + load local state.\n\t *\n\t * @return {Promise<void>}\n\t */\n\tasync mounted() {\n\t\twindow.addEventListener('online', this.onOnline)\n\t\twindow.addEventListener('offline', this.onOffline)\n\t\tawait this.loadLocal()\n\t},\n\n\t/**\n\t * Detach the online/offline listeners.\n\t *\n\t * @return {void}\n\t */\n\tbeforeUnmount() {\n\t\twindow.removeEventListener('online', this.onOnline)\n\t\twindow.removeEventListener('offline', this.onOffline)\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\titemId(item) {\n\t\t\treturn String(item?.id ?? item?.['@self']?.id ?? item?.uuid ?? '')\n\t\t},\n\n\t\titemTitle(item) {\n\t\t\treturn item?.[this.config.titleField] || this.itemId(item)\n\t\t},\n\n\t\titemStatus(item) {\n\t\t\tconst status = item?.status || 'planned'\n\t\t\tconst map = {\n\t\t\t\tplanned: t('nextcloud-vue', 'Planned'),\n\t\t\t\tin_progress: t('nextcloud-vue', 'In progress'),\n\t\t\t\tsynced: t('nextcloud-vue', 'Synced'),\n\t\t\t\tconflict: t('nextcloud-vue', 'Conflict'),\n\t\t\t}\n\t\t\treturn map[status] || status\n\t\t},\n\n\t\tformatTime(iso) {\n\t\t\tif (!iso) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn new Date(iso).toLocaleString()\n\t\t},\n\n\t\terrorFor(questionId) {\n\t\t\tconst e = this.errors.find((x) => x.questionId === questionId)\n\t\t\treturn e ? e.message : ''\n\t\t},\n\n\t\t/**\n\t\t * Load cached planning + pending count from IndexedDB.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadLocal() {\n\t\t\tif (!this.effectiveRegister || !this.effectiveSchema) {\n\t\t\t\tthis.loading = false\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\ttry {\n\t\t\t\tthis.plannedItems = await getPlannedItems(this.effectiveRegister, this.effectiveSchema)\n\t\t\t\tthis.planningMeta = await getPlanningMeta(this.effectiveRegister, this.effectiveSchema)\n\t\t\t\tthis.pendingCount = await countPending(this.deviceId)\n\t\t\t\tthis.stuckCount = await countStuck(this.deviceId)\n\t\t\t} catch (e) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFieldInspectionCard] loadLocal failed', e)\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Download today's planning (planned items + referenced templates) via\n\t\t * the standard OR object API and store it offline.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync syncDay() {\n\t\t\tthis.syncing = true\n\t\t\ttry {\n\t\t\t\tconst cfg = this.config\n\t\t\t\tconst items = await fetchPlanning({\n\t\t\t\t\tregister: this.effectiveRegister,\n\t\t\t\t\tschema: this.effectiveSchema,\n\t\t\t\t\tassigneeField: cfg.assigneeField,\n\t\t\t\t\tassignee: this.assignee || (typeof OC !== 'undefined' && OC.getCurrentUser ? OC.getCurrentUser().uid : ''),\n\t\t\t\t\tdateField: cfg.dateField,\n\t\t\t\t})\n\t\t\t\tconst references = cfg.referenceSchema\n\t\t\t\t\t? await fetchReferences({ register: this.effectiveRegister, referenceSchema: cfg.referenceSchema })\n\t\t\t\t\t: []\n\t\t\t\tawait storePlanning({\n\t\t\t\t\tregister: this.effectiveRegister,\n\t\t\t\t\tschema: this.effectiveSchema,\n\t\t\t\t\titems,\n\t\t\t\t\treferences,\n\t\t\t\t\treferenceSchema: cfg.referenceSchema,\n\t\t\t\t})\n\t\t\t\tawait this.loadLocal()\n\t\t\t} catch (e) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFieldInspectionCard] syncDay failed', e)\n\t\t\t} finally {\n\t\t\t\tthis.syncing = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Open one planned item and load its checklist template from cache.\n\t\t *\n\t\t * @param {object} item The planned item.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync openItem(item) {\n\t\t\tthis.activeItem = item\n\t\t\tthis.errors = []\n\t\t\tconst cfg = this.config\n\t\t\tconst templateRef = item?.[cfg.templateRefField]\n\t\t\t// The app's template shape (sections, its own item keys and type\n\t\t\t// words) is read into the leaf's flat item list here, once.\n\t\t\tthis.activeTemplate = templateRef\n\t\t\t\t? normaliseChecklistTemplate(\n\t\t\t\t\t\tawait getCachedObject(this.effectiveRegister, cfg.referenceSchema, 'references', String(templateRef)),\n\t\t\t\t\t\tcfg,\n\t\t\t\t\t)\n\t\t\t\t: null\n\t\t\tconst initial = {}\n\t\t\tfor (const q of (this.activeTemplate?.items || [])) {\n\t\t\t\tinitial[q.questionId] = { answer: '', evidenceRefs: [] }\n\t\t\t}\n\t\t\tthis.answers = initial\n\t\t},\n\n\t\tcloseItem() {\n\t\t\tthis.activeItem = null\n\t\t\tthis.activeTemplate = null\n\t\t\tthis.answers = {}\n\t\t\tthis.errors = []\n\t\t\tthis.saveError = ''\n\t\t},\n\n\t\t/**\n\t\t * Capture the current GPS fix (tagged on every answer).\n\t\t *\n\t\t * @return {Promise<object|null>} The GPS reading or null.\n\t\t */\n\t\tasync captureGps() {\n\t\t\tif (typeof navigator === 'undefined' || !navigator.geolocation) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn await new Promise((resolve) => {\n\t\t\t\tnavigator.geolocation.getCurrentPosition(\n\t\t\t\t\t(pos) => resolve({ lat: pos.coords.latitude, lon: pos.coords.longitude, accuracy: pos.coords.accuracy }),\n\t\t\t\t\t() => resolve(null),\n\t\t\t\t\t{ timeout: 8000 },\n\t\t\t\t)\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Validate answers, then queue the run: an object create on\n\t\t * `resultSchema`, or, when the config names a `resultEndpoint`, one\n\t\t * `submit` to that endpoint.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync saveChecklist() {\n\t\t\tconst result = validateChecklistAnswers(this.activeTemplate, this.answers)\n\t\t\tthis.errors = result.errors\n\t\t\tif (result.valid === false) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.saving = true\n\t\t\ttry {\n\t\t\t\tconst cfg = this.config\n\t\t\t\tconst gps = await this.captureGps()\n\t\t\t\tconst submission = buildChecklistSubmission(cfg, {\n\t\t\t\t\tplannedItem: this.activeItem,\n\t\t\t\t\ttemplate: this.activeTemplate,\n\t\t\t\t\tanswers: this.answers,\n\t\t\t\t\tgps,\n\t\t\t\t\tgpsSource: classifyGps(gps).source,\n\t\t\t\t\tcapturedAt: new Date().toISOString(),\n\t\t\t\t\tcapturedOffline: this.offline,\n\t\t\t\t})\n\t\t\t\tawait enqueueMutation({\n\t\t\t\t\tdeviceId: this.deviceId,\n\t\t\t\t\toperationType: submission.operationType,\n\t\t\t\t\tregister: this.effectiveRegister,\n\t\t\t\t\tschema: submission.schema,\n\t\t\t\t\tendpoint: submission.endpoint,\n\t\t\t\t\tpayload: submission.payload,\n\t\t\t\t})\n\t\t\t\tthis.pendingCount = await countPending(this.deviceId)\n\t\t\t\tthis.stuckCount = await countStuck(this.deviceId)\n\t\t\t\tthis.closeItem()\n\t\t\t\tif (this.offline === false) {\n\t\t\t\t\tawait this.drain()\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\tthis.saveError = e?.message || t('nextcloud-vue', 'The answers could not be saved on this device.')\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFieldInspectionCard] saveChecklist failed', e)\n\t\t\t} finally {\n\t\t\t\tthis.saving = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Drain the local mutation queue against the OR object API.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync drain() {\n\t\t\tthis.syncing = true\n\t\t\ttry {\n\t\t\t\tawait drainQueue(this.deviceId, this.drainConfig)\n\t\t\t\tthis.pendingCount = await countPending(this.deviceId)\n\t\t\t\tthis.stuckCount = await countStuck(this.deviceId)\n\t\t\t} catch (e) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.error('[CnFieldInspectionCard] drain failed', e)\n\t\t\t} finally {\n\t\t\t\tthis.syncing = false\n\t\t\t}\n\t\t},\n\n\t\tonOnline() {\n\t\t\tthis.offline = false\n\t\t\tthis.drain()\n\t\t},\n\n\t\tonOffline() {\n\t\t\tthis.offline = true\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-field-inspection {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tpadding: 4px 0;\n}\n\n.cn-field-inspection__sync {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 0.9em;\n}\n\n.cn-field-inspection__dot {\n\twidth: 10px;\n\theight: 10px;\n\tborder-radius: 50%;\n}\n\n.cn-field-inspection__sync--success .cn-field-inspection__dot { background: var(--color-success); }\n\n.cn-field-inspection__sync--warning .cn-field-inspection__dot { background: var(--color-warning); }\n\n.cn-field-inspection__sync--error .cn-field-inspection__dot { background: var(--color-error); }\n\n.cn-field-inspection__actions {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n\tflex-wrap: wrap;\n}\n\n.cn-field-inspection__meta {\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-field-inspection__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n}\n\n.cn-field-inspection__row-btn {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n\twidth: 100%;\n\tpadding: 12px 14px;\n\tmin-height: 44px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large);\n\tbackground: var(--color-main-background);\n\tcursor: pointer;\n\tcolor: var(--color-main-text);\n}\n\n.cn-field-inspection__row-btn:hover {\n\tbackground: var(--color-background-hover);\n}\n\n.cn-field-inspection__row-status {\n\tfont-size: 0.85em;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-field-inspection__checklist-head {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n}\n\n.cn-field-inspection__progress {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-field-inspection__question {\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius);\n\tpadding: 12px;\n\tmargin-bottom: 12px;\n}\n\n.cn-field-inspection__input {\n\twidth: 100%;\n\tmin-height: 44px;\n\tmargin-top: 6px;\n}\n\n.cn-field-inspection__required { color: var(--color-error); }\n\n.cn-field-inspection__error {\n\tcolor: var(--color-error);\n\tfont-size: 0.85em;\n\tdisplay: block;\n\tmargin-top: 4px;\n}\n</style>\n"],"names":["_createBlock","_createElementVNode","_normalizeClass","_toDisplayString","_createVNode","_createTextVNode","_createElementBlock","_openBlock","_Fragment","_renderList","_vModelSelect","_vModelText"],"mappings":"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"}
@@ -57,6 +57,27 @@ const DEFAULT_FIELD_INSPECTION_CONFIG = {
57
57
  // Schema holding conflict records. Empty keeps a conflict on the device,
58
58
  // and the queue row says so rather than pretending it was filed.
59
59
  conflictSchema: '',
60
+ // How a template's items are read (normaliseChecklistTemplate). Empty
61
+ // `sectionsField` reads a flat `items[]`; set it when the items sit inside
62
+ // ordered sections, each with its own `items[]`.
63
+ sectionsField: '',
64
+ itemKeyField: 'questionId',
65
+ itemTextField: 'text',
66
+ itemTypeField: 'type',
67
+ // The app's own item type words onto the leaf's (`yes_no`,
68
+ // `photo_required`; anything else renders as free text).
69
+ itemTypeMap: {},
70
+ // An item whose `photoRequiredField` equals `photoRequiredValue` needs a
71
+ // photo as well as an answer. Empty switches the gate off.
72
+ photoRequiredField: '',
73
+ photoRequiredValue: '',
74
+ // Where a finished run goes (checklistSubmission). Empty queues an object
75
+ // create on `resultSchema`. Set, it is a path under the Nextcloud root with
76
+ // `{field}` placeholders from the planned item, and the run replays as one
77
+ // POST there, naming the template and the planned item under these keys.
78
+ resultEndpoint: '',
79
+ resultTemplateParam: 'templateId',
80
+ resultPlannedItemParam: 'plannedItem',
60
81
  };
61
82
 
62
83
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"field-inspection.js","sources":["../../../../src/integrations/builtin/field-inspection.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n * SPDX-License-Identifier: EUPL-1.2\n *\n * Built-in `field-inspection` integration registration (offline field\n * data-collection leaf).\n *\n * Surfaces the generic offline data-collection core (IndexedDB cache, mutation\n * queue, replay-on-reconnect, sync-state indicator — see\n * `src/integrations/offline/`) as a registry leaf with a sidebar tab and a\n * surface-aware widget. The checklist fields and the \"today's planned items\"\n * query are NOT hardcoded: they come from the integration's `offlineConfig`,\n * which a consuming app supplies when it registers an override of this id (or\n * relies on the per-object `integrationContext` for register/schema).\n *\n * Default registration uses the canonical `fieldInspection` / `checklistResult`\n * schema names; a consuming app (e.g. procest) registers the SAME id first with\n * its own `offlineConfig` to point the leaf at its schemas — AD-13 first-wins\n * collision policy keeps the consumer's config.\n *\n * @module integrations/builtin/field-inspection\n */\n\nimport { translate as t } from '@nextcloud/l10n'\nimport CnFieldInspectionCard from './field-inspection/CnFieldInspectionCard.vue'\nimport CnFieldInspectionTab from './field-inspection/CnFieldInspectionTab.vue'\n\n/**\n * Default offline configuration for the field-inspection leaf.\n *\n * A consuming app overrides any of these by registering the `field-inspection`\n * id with its own `offlineConfig` before `registerBuiltinIntegrations()` runs.\n *\n * @type {object}\n */\nexport const DEFAULT_FIELD_INSPECTION_CONFIG = {\n\t// Schema holding the planned items (the inspections to do today).\n\tplannedSchema: 'fieldInspection',\n\t// Schema holding the checklist templates referenced by planned items.\n\treferenceSchema: 'inspectionChecklist',\n\t// Property on a planned item that references its checklist template.\n\ttemplateRefField: 'checklistTemplateRef',\n\t// Schema the completed checklist result is written back to (the queued create).\n\tresultSchema: 'checklistResult',\n\t// Planning filter: property holding the assignee + the scheduled-date field.\n\tassigneeField: 'inspectorRef',\n\tdateField: 'scheduledAt',\n\t// Property on a planned item used as its display title.\n\ttitleField: 'caseRef',\n\t// Register the leaf writes its own bookkeeping to. Empty falls back to the\n\t// register each queued operation already names.\n\tregister: '',\n\t// Schema holding queue records, for a consuming app that wants the queue\n\t// itself readable server-side. Empty writes nothing.\n\tqueueSchema: '',\n\t// Schema holding conflict records. Empty keeps a conflict on the device,\n\t// and the queue row says so rather than pretending it was filed.\n\tconflictSchema: '',\n}\n\n/**\n * `field-inspection` integration descriptor.\n *\n * @type {object}\n */\nexport const fieldInspectionIntegration = {\n\tid: 'field-inspection',\n\tlabel: t('nextcloud-vue', 'Field inspections'),\n\tappName: t('nextcloud-vue', 'Field inspections'),\n\ticon: 'ClipboardCheckOutline',\n\torder: 68,\n\tgroup: 'workflow',\n\treferenceType: 'field-inspection',\n\taccentColor: '#21468B',\n\tofflineConfig: DEFAULT_FIELD_INSPECTION_CONFIG,\n\ttab: CnFieldInspectionTab,\n\twidget: CnFieldInspectionCard,\n\tdefaultSize: { w: 4, h: 4 },\n}\n\nexport default fieldInspectionIntegration\n"],"names":["t","CnFieldInspectionTab","CnFieldInspectionCard"],"mappings":";;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,+BAA+B,GAAG;AAC/C;AACA,CAAC,aAAa,EAAE,iBAAiB;AACjC;AACA,CAAC,eAAe,EAAE,qBAAqB;AACvC;AACA,CAAC,gBAAgB,EAAE,sBAAsB;AACzC;AACA,CAAC,YAAY,EAAE,iBAAiB;AAChC;AACA,CAAC,aAAa,EAAE,cAAc;AAC9B,CAAC,SAAS,EAAE,aAAa;AACzB;AACA,CAAC,UAAU,EAAE,SAAS;AACtB;AACA;AACA,CAAC,QAAQ,EAAE,EAAE;AACb;AACA;AACA,CAAC,WAAW,EAAE,EAAE;AAChB;AACA;AACA,CAAC,cAAc,EAAE,EAAE;AACnB,EAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,0BAA0B,GAAG;AAC1C,CAAC,EAAE,EAAE,kBAAkB;AACvB,CAAC,KAAK,EAAEA,SAAC,CAAC,eAAe,EAAE,mBAAmB,CAAC;AAC/C,CAAC,OAAO,EAAEA,SAAC,CAAC,eAAe,EAAE,mBAAmB,CAAC;AACjD,CAAC,IAAI,EAAE,uBAAuB;AAC9B,CAAC,KAAK,EAAE,EAAE;AACV,CAAC,KAAK,EAAE,UAAU;AAClB,CAAC,aAAa,EAAE,kBAAkB;AAClC,CAAC,WAAW,EAAE,SAAS;AACvB,CAAC,aAAa,EAAE,+BAA+B;AAC/C,CAAC,GAAG,EAAEC,UAAoB;AAC1B,CAAC,MAAM,EAAEC,YAAqB;AAC9B,CAAC,WAAW,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AAC5B;;;;"}
1
+ {"version":3,"file":"field-inspection.js","sources":["../../../../src/integrations/builtin/field-inspection.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n * SPDX-License-Identifier: EUPL-1.2\n *\n * Built-in `field-inspection` integration registration (offline field\n * data-collection leaf).\n *\n * Surfaces the generic offline data-collection core (IndexedDB cache, mutation\n * queue, replay-on-reconnect, sync-state indicator — see\n * `src/integrations/offline/`) as a registry leaf with a sidebar tab and a\n * surface-aware widget. The checklist fields and the \"today's planned items\"\n * query are NOT hardcoded: they come from the integration's `offlineConfig`,\n * which a consuming app supplies when it registers an override of this id (or\n * relies on the per-object `integrationContext` for register/schema).\n *\n * Default registration uses the canonical `fieldInspection` / `checklistResult`\n * schema names; a consuming app (e.g. procest) registers the SAME id first with\n * its own `offlineConfig` to point the leaf at its schemas — AD-13 first-wins\n * collision policy keeps the consumer's config.\n *\n * @module integrations/builtin/field-inspection\n */\n\nimport { translate as t } from '@nextcloud/l10n'\nimport CnFieldInspectionCard from './field-inspection/CnFieldInspectionCard.vue'\nimport CnFieldInspectionTab from './field-inspection/CnFieldInspectionTab.vue'\n\n/**\n * Default offline configuration for the field-inspection leaf.\n *\n * A consuming app overrides any of these by registering the `field-inspection`\n * id with its own `offlineConfig` before `registerBuiltinIntegrations()` runs.\n *\n * @type {object}\n */\nexport const DEFAULT_FIELD_INSPECTION_CONFIG = {\n\t// Schema holding the planned items (the inspections to do today).\n\tplannedSchema: 'fieldInspection',\n\t// Schema holding the checklist templates referenced by planned items.\n\treferenceSchema: 'inspectionChecklist',\n\t// Property on a planned item that references its checklist template.\n\ttemplateRefField: 'checklistTemplateRef',\n\t// Schema the completed checklist result is written back to (the queued create).\n\tresultSchema: 'checklistResult',\n\t// Planning filter: property holding the assignee + the scheduled-date field.\n\tassigneeField: 'inspectorRef',\n\tdateField: 'scheduledAt',\n\t// Property on a planned item used as its display title.\n\ttitleField: 'caseRef',\n\t// Register the leaf writes its own bookkeeping to. Empty falls back to the\n\t// register each queued operation already names.\n\tregister: '',\n\t// Schema holding queue records, for a consuming app that wants the queue\n\t// itself readable server-side. Empty writes nothing.\n\tqueueSchema: '',\n\t// Schema holding conflict records. Empty keeps a conflict on the device,\n\t// and the queue row says so rather than pretending it was filed.\n\tconflictSchema: '',\n\t// How a template's items are read (normaliseChecklistTemplate). Empty\n\t// `sectionsField` reads a flat `items[]`; set it when the items sit inside\n\t// ordered sections, each with its own `items[]`.\n\tsectionsField: '',\n\titemKeyField: 'questionId',\n\titemTextField: 'text',\n\titemTypeField: 'type',\n\t// The app's own item type words onto the leaf's (`yes_no`,\n\t// `photo_required`; anything else renders as free text).\n\titemTypeMap: {},\n\t// An item whose `photoRequiredField` equals `photoRequiredValue` needs a\n\t// photo as well as an answer. Empty switches the gate off.\n\tphotoRequiredField: '',\n\tphotoRequiredValue: '',\n\t// Where a finished run goes (checklistSubmission). Empty queues an object\n\t// create on `resultSchema`. Set, it is a path under the Nextcloud root with\n\t// `{field}` placeholders from the planned item, and the run replays as one\n\t// POST there, naming the template and the planned item under these keys.\n\tresultEndpoint: '',\n\tresultTemplateParam: 'templateId',\n\tresultPlannedItemParam: 'plannedItem',\n}\n\n/**\n * `field-inspection` integration descriptor.\n *\n * @type {object}\n */\nexport const fieldInspectionIntegration = {\n\tid: 'field-inspection',\n\tlabel: t('nextcloud-vue', 'Field inspections'),\n\tappName: t('nextcloud-vue', 'Field inspections'),\n\ticon: 'ClipboardCheckOutline',\n\torder: 68,\n\tgroup: 'workflow',\n\treferenceType: 'field-inspection',\n\taccentColor: '#21468B',\n\tofflineConfig: DEFAULT_FIELD_INSPECTION_CONFIG,\n\ttab: CnFieldInspectionTab,\n\twidget: CnFieldInspectionCard,\n\tdefaultSize: { w: 4, h: 4 },\n}\n\nexport default fieldInspectionIntegration\n"],"names":["t","CnFieldInspectionTab","CnFieldInspectionCard"],"mappings":";;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,+BAA+B,GAAG;AAC/C;AACA,CAAC,aAAa,EAAE,iBAAiB;AACjC;AACA,CAAC,eAAe,EAAE,qBAAqB;AACvC;AACA,CAAC,gBAAgB,EAAE,sBAAsB;AACzC;AACA,CAAC,YAAY,EAAE,iBAAiB;AAChC;AACA,CAAC,aAAa,EAAE,cAAc;AAC9B,CAAC,SAAS,EAAE,aAAa;AACzB;AACA,CAAC,UAAU,EAAE,SAAS;AACtB;AACA;AACA,CAAC,QAAQ,EAAE,EAAE;AACb;AACA;AACA,CAAC,WAAW,EAAE,EAAE;AAChB;AACA;AACA,CAAC,cAAc,EAAE,EAAE;AACnB;AACA;AACA;AACA,CAAC,aAAa,EAAE,EAAE;AAClB,CAAC,YAAY,EAAE,YAAY;AAC3B,CAAC,aAAa,EAAE,MAAM;AACtB,CAAC,aAAa,EAAE,MAAM;AACtB;AACA;AACA,CAAC,WAAW,EAAE,EAAE;AAChB;AACA;AACA,CAAC,kBAAkB,EAAE,EAAE;AACvB,CAAC,kBAAkB,EAAE,EAAE;AACvB;AACA;AACA;AACA;AACA,CAAC,cAAc,EAAE,EAAE;AACnB,CAAC,mBAAmB,EAAE,YAAY;AAClC,CAAC,sBAAsB,EAAE,aAAa;AACtC,EAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,0BAA0B,GAAG;AAC1C,CAAC,EAAE,EAAE,kBAAkB;AACvB,CAAC,KAAK,EAAEA,SAAC,CAAC,eAAe,EAAE,mBAAmB,CAAC;AAC/C,CAAC,OAAO,EAAEA,SAAC,CAAC,eAAe,EAAE,mBAAmB,CAAC;AACjD,CAAC,IAAI,EAAE,uBAAuB;AAC9B,CAAC,KAAK,EAAE,EAAE;AACV,CAAC,KAAK,EAAE,UAAU;AAClB,CAAC,aAAa,EAAE,kBAAkB;AAClC,CAAC,WAAW,EAAE,SAAS;AACvB,CAAC,aAAa,EAAE,+BAA+B;AAC/C,CAAC,GAAG,EAAEC,UAAoB;AAC1B,CAAC,MAAM,EAAEC,YAAqB;AAC9B,CAAC,WAAW,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AAC5B;;;;"}