@cccteam/ccc-lib 0.0.13 → 0.0.15

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 (226) hide show
  1. package/eslint.config.js +32 -0
  2. package/ng-package.json +11 -0
  3. package/package.json +7 -68
  4. package/src/auth-authentication-guard/authentication.guard.ts +40 -0
  5. package/src/auth-authentication-guard/ng-package.json +6 -0
  6. package/src/auth-authorization-guard/authorization.guard.ts +17 -0
  7. package/src/auth-authorization-guard/ng-package.json +6 -0
  8. package/src/auth-forms/ccc-field/ccc-field.component.html +1 -0
  9. package/src/auth-forms/ccc-field/ccc-field.component.scss +0 -0
  10. package/src/auth-forms/ccc-field/ccc-field.component.spec.ts +22 -0
  11. package/src/auth-forms/ccc-field/ccc-field.component.ts +74 -0
  12. package/src/auth-forms/form-helpers.ts +39 -0
  13. package/src/auth-forms/{public-api.d.ts → index.ts} +1 -0
  14. package/src/auth-forms/ng-package.json +6 -0
  15. package/src/auth-has-permission/has-permission.directive.ts +34 -0
  16. package/src/auth-has-permission/ng-package.json +6 -0
  17. package/src/auth-service/auth.service.ts +92 -0
  18. package/src/auth-service/ng-package.json +6 -0
  19. package/src/ccc-camel-case-to-title/camel-case-to-title.pipe.ts +23 -0
  20. package/src/ccc-camel-case-to-title/index.ts +1 -0
  21. package/src/ccc-camel-case-to-title/ng-package.json +6 -0
  22. package/src/ccc-grid/ccc-grid.component.ts +155 -0
  23. package/src/ccc-grid/index.ts +3 -0
  24. package/src/ccc-grid/ng-package.json +6 -0
  25. package/src/ccc-grid/table-button/table-button.component.html +16 -0
  26. package/src/ccc-grid/table-button/table-button.component.scss +5 -0
  27. package/src/ccc-grid/table-button/table-button.component.spec.ts +22 -0
  28. package/src/ccc-grid/table-button/table-button.component.ts +49 -0
  29. package/src/ccc-resource/can-deactivate.guard.ts +41 -0
  30. package/src/ccc-resource/compound-resource/compound-resource.component.html +57 -0
  31. package/src/ccc-resource/compound-resource/compound-resource.component.scss +86 -0
  32. package/src/ccc-resource/compound-resource/compound-resource.component.spec.ts +22 -0
  33. package/src/ccc-resource/compound-resource/compound-resource.component.ts +158 -0
  34. package/src/ccc-resource/concat-fns.ts +162 -0
  35. package/src/ccc-resource/empty-readonly-field/empty-readonly-field.component.html +12 -0
  36. package/src/ccc-resource/empty-readonly-field/empty-readonly-field.component.scss +0 -0
  37. package/src/ccc-resource/empty-readonly-field/empty-readonly-field.component.spec.ts +23 -0
  38. package/src/ccc-resource/empty-readonly-field/empty-readonly-field.component.ts +17 -0
  39. package/src/ccc-resource/form-state.service.ts +24 -0
  40. package/src/ccc-resource/format-fns.ts +49 -0
  41. package/src/ccc-resource/gui-constants.ts +88 -0
  42. package/src/ccc-resource/index.ts +23 -0
  43. package/src/ccc-resource/leave-page-confirmation-modal/leave-page-confirmation-modal.component.html +8 -0
  44. package/src/ccc-resource/leave-page-confirmation-modal/leave-page-confirmation-modal.component.scss +0 -0
  45. package/src/ccc-resource/leave-page-confirmation-modal/leave-page-confirmation-modal.component.spec.ts +22 -0
  46. package/src/ccc-resource/leave-page-confirmation-modal/leave-page-confirmation-modal.component.ts +12 -0
  47. package/src/ccc-resource/ng-package.json +6 -0
  48. package/src/ccc-resource/operation-types.ts +19 -0
  49. package/src/ccc-resource/padding-element/padding-element.component.html +1 -0
  50. package/src/ccc-resource/padding-element/padding-element.component.scss +3 -0
  51. package/src/ccc-resource/padding-element/padding-element.component.spec.ts +22 -0
  52. package/src/ccc-resource/padding-element/padding-element.component.ts +20 -0
  53. package/src/ccc-resource/resource-array-view/resource-array-view.component.html +81 -0
  54. package/src/ccc-resource/resource-array-view/resource-array-view.component.scss +21 -0
  55. package/src/ccc-resource/resource-array-view/resource-array-view.component.spec.ts +22 -0
  56. package/src/ccc-resource/resource-array-view/resource-array-view.component.ts +143 -0
  57. package/src/ccc-resource/resource-base/resource-base.component.spec.ts +22 -0
  58. package/src/ccc-resource/resource-base/resource-base.component.ts +11 -0
  59. package/src/ccc-resource/resource-cache.service.ts +232 -0
  60. package/src/ccc-resource/resource-create/resource-create.component.html +31 -0
  61. package/src/ccc-resource/resource-create/resource-create.component.scss +130 -0
  62. package/src/ccc-resource/resource-create/resource-create.component.spec.ts +22 -0
  63. package/src/ccc-resource/resource-create/resource-create.component.ts +303 -0
  64. package/src/ccc-resource/resource-field/base-field.directive.ts +102 -0
  65. package/src/ccc-resource/resource-field/fields/boolean-field/boolean-field.component.html +16 -0
  66. package/src/ccc-resource/resource-field/fields/boolean-field/boolean-field.component.scss +0 -0
  67. package/src/ccc-resource/resource-field/fields/boolean-field/boolean-field.component.spec.ts +22 -0
  68. package/src/ccc-resource/resource-field/fields/boolean-field/boolean-field.component.ts +15 -0
  69. package/src/ccc-resource/resource-field/fields/computed-field/computed-field.component.html +13 -0
  70. package/src/ccc-resource/resource-field/fields/computed-field/computed-field.component.scss +0 -0
  71. package/src/ccc-resource/resource-field/fields/computed-field/computed-field.component.spec.ts +23 -0
  72. package/src/ccc-resource/resource-field/fields/computed-field/computed-field.component.ts +50 -0
  73. package/src/ccc-resource/resource-field/fields/date-field/date-field.component.html +22 -0
  74. package/src/ccc-resource/resource-field/fields/date-field/date-field.component.scss +0 -0
  75. package/src/ccc-resource/resource-field/fields/date-field/date-field.component.spec.ts +22 -0
  76. package/src/ccc-resource/resource-field/fields/date-field/date-field.component.ts +14 -0
  77. package/src/ccc-resource/resource-field/fields/enumerated-field/enumerated-field.component.html +71 -0
  78. package/src/ccc-resource/resource-field/fields/enumerated-field/enumerated-field.component.scss +9 -0
  79. package/src/ccc-resource/resource-field/fields/enumerated-field/enumerated-field.component.spec.ts +22 -0
  80. package/src/ccc-resource/resource-field/fields/enumerated-field/enumerated-field.component.ts +207 -0
  81. package/src/ccc-resource/resource-field/fields/nullboolean-field/nullboolean-field.component.html +38 -0
  82. package/src/ccc-resource/resource-field/fields/nullboolean-field/nullboolean-field.component.scss +3 -0
  83. package/src/ccc-resource/resource-field/fields/nullboolean-field/nullboolean-field.component.spec.ts +22 -0
  84. package/src/ccc-resource/resource-field/fields/nullboolean-field/nullboolean-field.component.ts +87 -0
  85. package/src/ccc-resource/resource-field/fields/number-field/number-field.component.html +23 -0
  86. package/src/ccc-resource/resource-field/fields/number-field/number-field.component.scss +6 -0
  87. package/src/ccc-resource/resource-field/fields/number-field/number-field.component.spec.ts +22 -0
  88. package/src/ccc-resource/resource-field/fields/number-field/number-field.component.ts +14 -0
  89. package/src/ccc-resource/resource-field/fields/text-field/text-field.component.html +29 -0
  90. package/src/ccc-resource/resource-field/fields/text-field/text-field.component.scss +6 -0
  91. package/src/ccc-resource/resource-field/fields/text-field/text-field.component.spec.ts +22 -0
  92. package/src/ccc-resource/resource-field/fields/text-field/text-field.component.ts +23 -0
  93. package/src/ccc-resource/resource-field/resource-field.component.html +112 -0
  94. package/src/ccc-resource/resource-field/resource-field.component.scss +7 -0
  95. package/src/ccc-resource/resource-field/resource-field.component.spec.ts +22 -0
  96. package/src/ccc-resource/resource-field/resource-field.component.ts +214 -0
  97. package/src/ccc-resource/resource-layout/resource-layout.component.html +73 -0
  98. package/src/ccc-resource/resource-layout/resource-layout.component.scss +26 -0
  99. package/src/ccc-resource/resource-layout/resource-layout.component.spec.ts +22 -0
  100. package/src/ccc-resource/resource-layout/resource-layout.component.ts +176 -0
  101. package/src/ccc-resource/resource-list/ resource-list.component.spec.ts +22 -0
  102. package/src/ccc-resource/resource-list/resource-list.component.html +27 -0
  103. package/src/ccc-resource/resource-list/resource-list.component.scss +67 -0
  104. package/src/ccc-resource/resource-list/resource-list.component.ts +376 -0
  105. package/src/ccc-resource/resource-list-create/resource-list-create.component.html +71 -0
  106. package/src/ccc-resource/resource-list-create/resource-list-create.component.scss +9 -0
  107. package/src/ccc-resource/resource-list-create/resource-list-create.component.spec.ts +22 -0
  108. package/src/ccc-resource/resource-list-create/resource-list-create.component.ts +103 -0
  109. package/src/ccc-resource/resource-resolver/resource-resolver.component.html +1 -0
  110. package/src/ccc-resource/resource-resolver/resource-resolver.component.scss +0 -0
  111. package/src/ccc-resource/resource-resolver/resource-resolver.component.spec.ts +22 -0
  112. package/src/ccc-resource/resource-resolver/resource-resolver.component.ts +69 -0
  113. package/src/ccc-resource/resource-store.service.ts +93 -0
  114. package/src/ccc-resource/resource-view/resource-view.component.html +133 -0
  115. package/src/ccc-resource/resource-view/resource-view.component.scss +150 -0
  116. package/src/ccc-resource/resource-view/resource-view.component.spec.ts +22 -0
  117. package/src/ccc-resource/resource-view/resource-view.component.ts +354 -0
  118. package/src/ccc-resource/resources-helpers.ts +262 -0
  119. package/src/ccc-resource/utils/validator-utils.ts +6 -0
  120. package/{fesm2022/cccteam-ccc-lib.mjs → src/index.ts} +32 -11
  121. package/src/internal-types/index.ts +1 -0
  122. package/src/internal-types/ng-package.json +6 -0
  123. package/src/types/auth.actions.ts +46 -0
  124. package/src/types/configs.ts +952 -0
  125. package/src/types/constants.ts +1 -0
  126. package/src/types/core.actions.ts +33 -0
  127. package/src/types/{public-api.d.ts → index.ts} +3 -0
  128. package/src/types/ng-package.json +6 -0
  129. package/src/types/notification-message.ts +20 -0
  130. package/src/types/{permissions.d.ts → permissions.ts} +9 -9
  131. package/src/types/{session-info.d.ts → session-info.ts} +4 -3
  132. package/src/types/tokens.ts +20 -0
  133. package/src/ui-alert/alert.component.html +13 -0
  134. package/src/ui-alert/alert.component.scss +48 -0
  135. package/src/ui-alert/alert.component.spec.ts +22 -0
  136. package/src/ui-alert/alert.component.ts +35 -0
  137. package/src/ui-alert/ng-package.json +6 -0
  138. package/src/ui-core-service/index.ts +1 -0
  139. package/src/ui-core-service/ng-package.json +6 -0
  140. package/src/ui-core-service/ui-core.service.ts +34 -0
  141. package/src/ui-interceptor/api.interceptor.spec.ts +16 -0
  142. package/src/ui-interceptor/api.interceptor.ts +45 -0
  143. package/src/ui-interceptor/ng-package.json +6 -0
  144. package/src/ui-notification-service/ng-package.json +6 -0
  145. package/src/ui-notification-service/notification.service.ts +59 -0
  146. package/src/ui-sidenav/ng-package.json +6 -0
  147. package/src/ui-sidenav/sidenav.component.html +60 -0
  148. package/src/ui-sidenav/sidenav.component.scss +99 -0
  149. package/src/ui-sidenav/sidenav.component.spec.ts +22 -0
  150. package/src/ui-sidenav/sidenav.component.ts +64 -0
  151. package/src/util-request-options/ng-package.json +6 -0
  152. package/src/util-request-options/request-options.ts +17 -0
  153. package/tsconfig.lib.json +13 -0
  154. package/tsconfig.lib.prod.json +11 -0
  155. package/tsconfig.spec.json +15 -0
  156. package/cccteam-ccc-lib-0.0.13.tgz +0 -0
  157. package/fesm2022/cccteam-ccc-lib-src-auth-authentication-guard.mjs +0 -44
  158. package/fesm2022/cccteam-ccc-lib-src-auth-authentication-guard.mjs.map +0 -1
  159. package/fesm2022/cccteam-ccc-lib-src-auth-authorization-guard.mjs +0 -24
  160. package/fesm2022/cccteam-ccc-lib-src-auth-authorization-guard.mjs.map +0 -1
  161. package/fesm2022/cccteam-ccc-lib-src-auth-forms.mjs +0 -118
  162. package/fesm2022/cccteam-ccc-lib-src-auth-forms.mjs.map +0 -1
  163. package/fesm2022/cccteam-ccc-lib-src-auth-has-permission.mjs +0 -51
  164. package/fesm2022/cccteam-ccc-lib-src-auth-has-permission.mjs.map +0 -1
  165. package/fesm2022/cccteam-ccc-lib-src-auth-service.mjs +0 -56
  166. package/fesm2022/cccteam-ccc-lib-src-auth-service.mjs.map +0 -1
  167. package/fesm2022/cccteam-ccc-lib-src-auth-state.mjs +0 -109
  168. package/fesm2022/cccteam-ccc-lib-src-auth-state.mjs.map +0 -1
  169. package/fesm2022/cccteam-ccc-lib-src-types.mjs +0 -137
  170. package/fesm2022/cccteam-ccc-lib-src-types.mjs.map +0 -1
  171. package/fesm2022/cccteam-ccc-lib-src-ui-alert.mjs +0 -48
  172. package/fesm2022/cccteam-ccc-lib-src-ui-alert.mjs.map +0 -1
  173. package/fesm2022/cccteam-ccc-lib-src-ui-core-state.mjs +0 -100
  174. package/fesm2022/cccteam-ccc-lib-src-ui-core-state.mjs.map +0 -1
  175. package/fesm2022/cccteam-ccc-lib-src-ui-interceptor.mjs +0 -48
  176. package/fesm2022/cccteam-ccc-lib-src-ui-interceptor.mjs.map +0 -1
  177. package/fesm2022/cccteam-ccc-lib-src-ui-notification-service.mjs +0 -57
  178. package/fesm2022/cccteam-ccc-lib-src-ui-notification-service.mjs.map +0 -1
  179. package/fesm2022/cccteam-ccc-lib-src-ui-sidenav.mjs +0 -70
  180. package/fesm2022/cccteam-ccc-lib-src-ui-sidenav.mjs.map +0 -1
  181. package/fesm2022/cccteam-ccc-lib-src-util-request-options.mjs +0 -19
  182. package/fesm2022/cccteam-ccc-lib-src-util-request-options.mjs.map +0 -1
  183. package/fesm2022/cccteam-ccc-lib.mjs.map +0 -1
  184. package/index.d.ts +0 -5
  185. package/public-api.d.ts +0 -13
  186. package/src/auth-authentication-guard/authentication.guard.d.ts +0 -3
  187. package/src/auth-authentication-guard/index.d.ts +0 -5
  188. package/src/auth-authorization-guard/authorization.guard.d.ts +0 -3
  189. package/src/auth-authorization-guard/index.d.ts +0 -5
  190. package/src/auth-forms/ccc-field/ccc-field.component.d.ts +0 -25
  191. package/src/auth-forms/form-helpers.d.ts +0 -16
  192. package/src/auth-forms/index.d.ts +0 -5
  193. package/src/auth-has-permission/has-permission.directive.d.ts +0 -12
  194. package/src/auth-has-permission/index.d.ts +0 -5
  195. package/src/auth-service/auth.service.d.ts +0 -24
  196. package/src/auth-service/index.d.ts +0 -5
  197. package/src/auth-state/auth.state.d.ts +0 -27
  198. package/src/auth-state/index.d.ts +0 -5
  199. package/src/auth-state/public-api.d.ts +0 -1
  200. package/src/types/auth.actions.d.ts +0 -41
  201. package/src/types/core.actions.d.ts +0 -31
  202. package/src/types/index.d.ts +0 -5
  203. package/src/types/notification-message.d.ts +0 -18
  204. package/src/types/tokens.d.ts +0 -13
  205. package/src/ui-alert/alert.component.d.ts +0 -13
  206. package/src/ui-alert/index.d.ts +0 -5
  207. package/src/ui-core-state/core.state.d.ts +0 -28
  208. package/src/ui-core-state/index.d.ts +0 -5
  209. package/src/ui-core-state/public-api.d.ts +0 -1
  210. package/src/ui-interceptor/api.interceptor.d.ts +0 -12
  211. package/src/ui-interceptor/index.d.ts +0 -5
  212. package/src/ui-notification-service/index.d.ts +0 -5
  213. package/src/ui-notification-service/notification.service.d.ts +0 -30
  214. package/src/ui-sidenav/index.d.ts +0 -5
  215. package/src/ui-sidenav/sidenav.component.d.ts +0 -31
  216. package/src/util-request-options/index.d.ts +0 -5
  217. package/src/util-request-options/request-options.d.ts +0 -8
  218. /package/src/auth-authentication-guard/{public-api.d.ts → index.ts} +0 -0
  219. /package/src/auth-authorization-guard/{public-api.d.ts → index.ts} +0 -0
  220. /package/src/auth-has-permission/{public-api.d.ts → index.ts} +0 -0
  221. /package/src/auth-service/{public-api.d.ts → index.ts} +0 -0
  222. /package/src/ui-alert/{public-api.d.ts → index.ts} +0 -0
  223. /package/src/ui-interceptor/{public-api.d.ts → index.ts} +0 -0
  224. /package/src/ui-notification-service/{public-api.d.ts → index.ts} +0 -0
  225. /package/src/ui-sidenav/{public-api.d.ts → index.ts} +0 -0
  226. /package/src/util-request-options/{public-api.d.ts → index.ts} +0 -0
@@ -0,0 +1,354 @@
1
+ import { Location } from '@angular/common';
2
+ import {
3
+ Component,
4
+ computed,
5
+ DestroyRef,
6
+ effect,
7
+ inject,
8
+ Injector,
9
+ input,
10
+ OnInit,
11
+ output,
12
+ signal,
13
+ WritableSignal,
14
+ } from '@angular/core';
15
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
16
+ import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
17
+ import { MatButtonModule } from '@angular/material/button';
18
+ import { MatDialog } from '@angular/material/dialog';
19
+ import { MatDividerModule } from '@angular/material/divider';
20
+ import { MatExpansionPanel, MatExpansionPanelHeader, MatExpansionPanelTitle } from '@angular/material/expansion';
21
+ import { MatIconModule } from '@angular/material/icon';
22
+ import { MatInputModule } from '@angular/material/input';
23
+ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
24
+ import { ActivatedRoute, Router, RouterModule } from '@angular/router';
25
+ import { sparseFormData } from '@cccteam/ccc-lib/src/auth-forms';
26
+ import {
27
+ AlertLevel,
28
+ DataType,
29
+ FieldElement,
30
+ ListViewConfig,
31
+ RecordData,
32
+ Resource,
33
+ RESOURCE_META,
34
+ ResourceMeta,
35
+ RootConfig,
36
+ RPCConfig,
37
+ ViewConfig,
38
+ } from '@cccteam/ccc-lib/src/types';
39
+ import { NotificationService } from '@cccteam/ccc-lib/src/ui-notification-service';
40
+ import { filter, tap } from 'rxjs';
41
+ import { FormStateService } from '../form-state.service';
42
+ import { civildateCoercion, flattenElements } from '../gui-constants';
43
+ import { DeleteOperation, UpdateOperation } from '../operation-types';
44
+ import { ResourceCacheService } from '../resource-cache.service';
45
+ import { ResourceLayoutComponent } from '../resource-layout/resource-layout.component';
46
+ import { ResourceStore } from '../resource-store.service';
47
+ import { metadataTypeCoercion } from '../resources-helpers';
48
+
49
+ @Component({
50
+ selector: 'ccc-resource-view',
51
+ imports: [
52
+ FormsModule,
53
+ ReactiveFormsModule,
54
+ MatInputModule,
55
+ RouterModule,
56
+ MatButtonModule,
57
+ MatIconModule,
58
+ MatProgressSpinnerModule,
59
+ MatDividerModule,
60
+ ResourceLayoutComponent,
61
+ MatExpansionPanel,
62
+ MatExpansionPanelHeader,
63
+ MatExpansionPanelTitle,
64
+ ],
65
+ templateUrl: './resource-view.component.html',
66
+ styleUrl: './resource-view.component.scss',
67
+ providers: [ResourceStore],
68
+ })
69
+ export class ResourceViewComponent implements OnInit {
70
+ location = inject(Location);
71
+ router = inject(Router);
72
+ activatedRoute = inject(ActivatedRoute);
73
+ notifications = inject(NotificationService);
74
+ store = inject(ResourceStore);
75
+ cache = inject(ResourceCacheService);
76
+ injector = inject(Injector);
77
+ destroyRef = inject(DestroyRef);
78
+ formState = inject(FormStateService);
79
+ dialog = inject(MatDialog);
80
+
81
+ editMode: WritableSignal<'edit' | 'view'> = signal('view');
82
+ uuid = input.required<string>();
83
+ config = input.required<ViewConfig | ListViewConfig>();
84
+ relatedData = input<RecordData>({});
85
+ compoundResourceView = input<boolean>(false);
86
+ isDirty = signal(false);
87
+
88
+ displayFormInvalidMessage = signal<boolean>(false);
89
+ deleted = output<boolean>();
90
+
91
+ resourceMeta = inject(RESOURCE_META);
92
+
93
+ rootConfig = computed(() => {
94
+ return this.activatedRoute.snapshot.data['config'] as RootConfig;
95
+ });
96
+
97
+ shouldShowDelete = computed(() => {
98
+ const config = this.config();
99
+ if (config) {
100
+ return config.primaryResource !== this.rootConfig().parentConfig.primaryResource;
101
+ }
102
+ return config === undefined;
103
+ });
104
+
105
+ inlineRpcConfigs = computed(() => {
106
+ const config = this.config();
107
+ if (config) {
108
+ return config.rpcConfigs?.filter((rpc: RPCConfig) => rpc.placement === 'inline');
109
+ }
110
+ return [];
111
+ });
112
+
113
+ endRpcConfigs = computed(() => {
114
+ const config = this.config();
115
+ if (config) {
116
+ return config.rpcConfigs?.filter((rpc: RPCConfig) => rpc.placement === 'end');
117
+ }
118
+ return [];
119
+ });
120
+
121
+ useExpansionPanel = computed(() => {
122
+ const config = this.config();
123
+ // TODO: Investigate why we're doing this check, it's weird because viewType is not part of the input configs
124
+ if (config && 'viewType' in config && config.viewType !== 'View') {
125
+ return false;
126
+ }
127
+
128
+ return config.collapsible || !this.compoundResourceView();
129
+ });
130
+
131
+ resourceConfigRouteSnapshot = computed(() => {
132
+ return (
133
+ this.config() ||
134
+ (this.activatedRoute.snapshot.data['config'] as RootConfig)?.parentConfig ||
135
+ ({} as ViewConfig | ListViewConfig)
136
+ );
137
+ });
138
+
139
+ emptyFormGroup = new FormGroup({});
140
+
141
+ form = computed(() => {
142
+ const meta = this.store.resourceMeta();
143
+ const resourceData = this.store.viewData();
144
+ const config = this.config();
145
+
146
+ if (meta === undefined || resourceData === undefined || config === undefined) {
147
+ return this.emptyFormGroup;
148
+ }
149
+
150
+ const fg = new FormGroup({});
151
+ const pristineValues: Record<string, DataType | null> = {};
152
+ const allElements = flattenElements(config.elements);
153
+
154
+ for (const field of meta.fields || []) {
155
+ const isFieldNameRegistered = fg.get(field.fieldName) !== null;
156
+ if (isFieldNameRegistered) {
157
+ continue;
158
+ }
159
+
160
+ const findConfig = allElements.find((element) => element.type === 'field' && element.name === field.fieldName);
161
+ const fieldConfig = findConfig as FieldElement | undefined;
162
+ if (!fieldConfig) {
163
+ continue;
164
+ }
165
+
166
+ let value: DataType | null = null;
167
+ const stringValue = resourceData?.[field.fieldName] ? String(resourceData[field.fieldName]) : '';
168
+
169
+ if (field.displayType === 'civildate' && stringValue) {
170
+ value = civildateCoercion(stringValue);
171
+ } else if (resourceData[field.fieldName] !== undefined) {
172
+ value = resourceData[field.fieldName];
173
+ }
174
+
175
+ const control = new FormControl(value);
176
+
177
+ if (fieldConfig.validators.length > 0) {
178
+ control.setValidators(fieldConfig.validators);
179
+ }
180
+
181
+ fg.addControl(field.fieldName, control);
182
+ pristineValues[field.fieldName] = value;
183
+ }
184
+ this.pristineFormValues = pristineValues;
185
+
186
+ return fg;
187
+ });
188
+
189
+ pristineFormValues: Record<string, DataType | null> = {};
190
+
191
+ route = computed(() => {
192
+ const meta = this.store.resourceMeta();
193
+ if (!meta) return '';
194
+ return meta.consolidatedRoute || meta.route;
195
+ });
196
+
197
+ primaryKeys = computed(() => {
198
+ const meta = this.store.resourceMeta() as ResourceMeta;
199
+ if (!meta) return [];
200
+ return meta.fields
201
+ .filter((field) => field.primaryKey)
202
+ .sort((a, b) => a.primaryKey!.ordinalPosition - b.primaryKey!.ordinalPosition);
203
+ });
204
+
205
+ primaryKeyPath = computed(() => {
206
+ const meta = this.store.resourceMeta();
207
+ const isConsolidated = !!meta.consolidatedRoute;
208
+ let resourceIdentifier = '';
209
+ if (isConsolidated) {
210
+ resourceIdentifier = '/' + String(meta.route);
211
+ }
212
+ return (
213
+ resourceIdentifier +
214
+ '/' +
215
+ this.primaryKeys()
216
+ .map((field) => this.store.viewData()[field.fieldName])
217
+ .join('/')
218
+ );
219
+ });
220
+
221
+ ngOnInit(): void {
222
+ if (this.resourceMeta(this.config().primaryResource as Resource)) {
223
+ this.store.resourceName.set(this.config().primaryResource as Resource);
224
+ this.store.resourceMeta.set(this.resourceMeta(this.config().primaryResource as Resource));
225
+ }
226
+
227
+ this.store.uuid.set(this.relatedId());
228
+ this.store.resetResourceView();
229
+ this.inlineRpcConfigs();
230
+ this.endRpcConfigs();
231
+ }
232
+
233
+ setEditMode(mode: 'edit' | 'view'): void {
234
+ if (mode === 'view') {
235
+ const changeData = sparseFormData(this.form(), this.pristineFormValues);
236
+ if (Object.keys(changeData).length !== 0) {
237
+ this.notifications.addGlobalNotification({
238
+ message: 'You have unsaved changes.',
239
+ link: '',
240
+ level: AlertLevel.ERROR,
241
+ });
242
+ return;
243
+ }
244
+ this.editMode.set('view');
245
+ } else if (mode === 'edit') {
246
+ this.editMode.set('edit');
247
+ }
248
+ }
249
+
250
+ saveForm(): void {
251
+ if (!this.form().valid) {
252
+ this.displayFormInvalidMessage.set(true);
253
+ this.form().markAllAsTouched();
254
+ return;
255
+ }
256
+
257
+ this.displayFormInvalidMessage.set(false);
258
+
259
+ const resourceMeta = this.store.resourceMeta();
260
+ if (!resourceMeta) return;
261
+
262
+ const sparseData = sparseFormData(this.form(), this.pristineFormValues);
263
+ const coercedSparseData = metadataTypeCoercion(sparseData, resourceMeta);
264
+
265
+ const updatePatch: UpdateOperation = {
266
+ op: 'patch',
267
+ value: coercedSparseData,
268
+ path: this.primaryKeyPath(),
269
+ };
270
+
271
+ this.pristineFormValues = this.form().getRawValue();
272
+
273
+ if (Object.keys(coercedSparseData).length === 0) return;
274
+ this.cache
275
+ .makePatches([updatePatch], this.route(), this.store.resourceName())
276
+ .pipe(
277
+ tap(() => {
278
+ this.form().markAsPristine();
279
+ this.setEditMode('view');
280
+ this.formState.decrementDirtyForms();
281
+ this.store.reloadResourceView();
282
+ }),
283
+ )
284
+ .subscribe();
285
+ }
286
+
287
+ resetForm(): void {
288
+ this.form().reset();
289
+ this.form().patchValue(this.pristineFormValues);
290
+ this.setEditMode('view');
291
+ if (this.form().dirty) {
292
+ this.formState.decrementDirtyForms();
293
+ }
294
+ }
295
+
296
+ deleteResource(): void {
297
+ const deletePatch: DeleteOperation = {
298
+ op: 'remove',
299
+ value: {},
300
+ path: this.primaryKeyPath(),
301
+ };
302
+
303
+ this.cache
304
+ .makePatches([deletePatch], this.route(), this.store.resourceName())
305
+ .pipe(
306
+ tap(() => {
307
+ this.cache.updateResourceInCache(this.store.resourceName(), 'list');
308
+ this.deleted.emit(true);
309
+ }),
310
+ filter(() => this.compoundResourceView()),
311
+ tap(() => {
312
+ this.location.back();
313
+ }),
314
+ )
315
+ .subscribe();
316
+ }
317
+
318
+ relatedId(): string {
319
+ const relatedData = this.relatedData();
320
+ const config = this.config();
321
+ if (config.parentRelation?.parentKey) {
322
+ return String(relatedData[config.parentRelation.parentKey]);
323
+ }
324
+ return this.uuid();
325
+ }
326
+
327
+ constructor() {
328
+ effect(() => {
329
+ console.debug('USAGE | New FormGroup subscription for: ', this.config().title);
330
+
331
+ this.form()
332
+ .valueChanges.pipe(
333
+ tap(() => {
334
+ const dirty = this.form().dirty;
335
+ if (dirty !== this.isDirty()) {
336
+ this.isDirty.set(dirty);
337
+ if (dirty) {
338
+ this.formState.incrementDirtyForms();
339
+ } else {
340
+ this.formState.decrementDirtyForms();
341
+ }
342
+ }
343
+ }),
344
+ takeUntilDestroyed(this.destroyRef),
345
+ )
346
+ .subscribe();
347
+ });
348
+
349
+ effect(() => {
350
+ this.uuid();
351
+ this.store.uuid.set(this.relatedId());
352
+ });
353
+ }
354
+ }
@@ -0,0 +1,262 @@
1
+ import { inject, ModelSignal } from '@angular/core';
2
+ import { AbstractControl, FormArray, FormControl, FormGroup } from '@angular/forms';
3
+ import { Route } from '@angular/router';
4
+ import type { Resource } from '@cccteam/ccc-lib/src/internal-types';
5
+ import { RESOURCE_META } from '@cccteam/ccc-lib/src/internal-types';
6
+ import {
7
+ ConfigElement,
8
+ DataType,
9
+ FieldElement,
10
+ MenuItem,
11
+ MethodMeta,
12
+ PristineData,
13
+ RecordData,
14
+ ResourceMeta,
15
+ RootConfig,
16
+ RouteResourceData,
17
+ RPCRecordData,
18
+ ViewConfig,
19
+ } from '@cccteam/ccc-lib/src/types';
20
+ import { format, isValid } from 'date-fns';
21
+ import { dirtyFormDeactivateGuard } from './can-deactivate.guard';
22
+ import { civildateCoercion, flattenElements } from './gui-constants';
23
+ import { ResourceBaseComponent } from './resource-base/resource-base.component';
24
+
25
+ /**
26
+ * Recursively cleans a FormGroup or FormArray, removing controls with empty string values.
27
+ * Similar to sparseFormData, but doesn't compare to an initial state
28
+ * @param control - The FormGroup or FormArray to clean.
29
+ * @returns A cleaned object with non-empty values.
30
+ */
31
+ export function cleanStringForm<T>(control: AbstractControl): T | undefined {
32
+ if (control instanceof FormGroup) {
33
+ const cleanedGroup: Record<string, unknown> = {};
34
+ for (const key of Object.keys(control.controls)) {
35
+ const childControl = control.get(key);
36
+ if (childControl) {
37
+ const cleanedValue = cleanStringForm(childControl);
38
+ if (cleanedValue !== undefined && cleanedValue !== null && cleanedValue !== '') {
39
+ cleanedGroup[key] = cleanedValue;
40
+ }
41
+ }
42
+ }
43
+ return cleanedGroup as T;
44
+ } else if (control instanceof FormArray) {
45
+ const cleanedArray = control.controls
46
+ .map((childControl) => cleanStringForm(childControl))
47
+ .filter((item) => item !== undefined && item !== null && item !== '');
48
+ return cleanedArray as unknown as T;
49
+ } else {
50
+ return control.value !== '' ? control.value : undefined;
51
+ }
52
+ }
53
+
54
+ export const createFormGroup = (
55
+ meta: ResourceMeta,
56
+ resourceData: RecordData,
57
+ config: ViewConfig,
58
+ formDataState?: ModelSignal<RecordData>,
59
+ ): {
60
+ formGroup: FormGroup;
61
+ pristineFormValues: PristineData;
62
+ } => {
63
+ const fg = new FormGroup({});
64
+ const pristineValues: Record<string, DataType | null> = {};
65
+ const allElements = flattenElements(config.elements);
66
+
67
+ for (const field of meta.fields || []) {
68
+ const isFieldNameRegistered = fg.get(field.fieldName) !== null;
69
+ if (isFieldNameRegistered) {
70
+ continue;
71
+ }
72
+
73
+ const findConfig = allElements.find((element) => element.type === 'field' && element.name === field.fieldName);
74
+ const fieldConfig = findConfig as FieldElement | undefined;
75
+ if (!fieldConfig) {
76
+ continue;
77
+ }
78
+
79
+ let value: DataType | null = null;
80
+ const stringValue = resourceData?.[field.fieldName] ? String(resourceData[field.fieldName]) : '';
81
+
82
+ if (field.displayType === 'civildate' && stringValue) {
83
+ value = civildateCoercion(stringValue);
84
+ } else if (resourceData[field.fieldName] !== undefined) {
85
+ value = resourceData[field.fieldName];
86
+ }
87
+
88
+ const control = new FormControl(value);
89
+
90
+ if (fieldConfig.validators.length > 0) {
91
+ control.setValidators(fieldConfig.validators);
92
+ }
93
+
94
+ fg.addControl(field.fieldName, control);
95
+ pristineValues[field.fieldName] = value;
96
+ }
97
+ // todo: swap this with a manual subscription where the form data is subscribed to and
98
+ // is dstroyed once the form is destroyed, similar to gui/src/app/components/Resource/resource-view/resource-view.component.ts
99
+ // constructor effect
100
+ console.log(formDataState);
101
+ // formDataState && formDataState.set(pristineValues);
102
+
103
+ return {
104
+ formGroup: fg,
105
+ pristineFormValues: pristineValues,
106
+ };
107
+ };
108
+
109
+ export const resourceRoutes = (config: RootConfig): Route => {
110
+ const resourceMeta = inject(RESOURCE_META);
111
+ const meta = resourceMeta(config.parentConfig.primaryResource as Resource);
112
+ if (!meta) {
113
+ return {} as Route;
114
+ }
115
+
116
+ if (config.nav) {
117
+ if (config.routeData.route) {
118
+ addToNavItems(config.nav, config.routeData.route);
119
+ } else {
120
+ addToNavItems(config.nav, meta.route);
121
+ }
122
+ }
123
+
124
+ if (config.routeData.route) {
125
+ const baseRoute: Route = {
126
+ path: config.routeData.route,
127
+ data: { config: config } satisfies RouteResourceData,
128
+ component: ResourceBaseComponent,
129
+ children: [
130
+ {
131
+ path: '',
132
+ loadComponent: () =>
133
+ import(`./resource-list-create/resource-list-create.component`).then(
134
+ (mod) => mod.ResourceListCreateComponent,
135
+ ),
136
+ canDeactivate: [dirtyFormDeactivateGuard],
137
+ },
138
+ ],
139
+ };
140
+ if (config.routeData.hasViewRoute !== false) {
141
+ baseRoute.children?.push({
142
+ path: ':uuid',
143
+ loadComponent: () =>
144
+ import(`./compound-resource/compound-resource.component`).then((mod) => mod.CompoundResourceComponent),
145
+ canDeactivate: [dirtyFormDeactivateGuard],
146
+ });
147
+ return baseRoute;
148
+ }
149
+ }
150
+
151
+ return {
152
+ path: meta.route,
153
+ data: { config: config } as RouteResourceData,
154
+ component: ResourceBaseComponent,
155
+ children: [
156
+ {
157
+ path: ':uuid',
158
+ loadComponent: () =>
159
+ import(`./compound-resource/compound-resource.component`).then((mod) => mod.CompoundResourceComponent),
160
+ canDeactivate: [dirtyFormDeactivateGuard],
161
+ },
162
+ {
163
+ path: '',
164
+ loadComponent: () =>
165
+ import(`./resource-list-create/resource-list-create.component`).then(
166
+ (mod) => mod.ResourceListCreateComponent,
167
+ ),
168
+ canDeactivate: [dirtyFormDeactivateGuard],
169
+ },
170
+ ],
171
+ } satisfies Route;
172
+ };
173
+
174
+ export const generatedNavItems = [] as MenuItem[];
175
+ export const generatedNavGroups = [] as string[];
176
+
177
+ function addToNavItems(
178
+ nav: {
179
+ navItem: MenuItem;
180
+ group?: string;
181
+ },
182
+ route: string,
183
+ ): void {
184
+ nav.navItem.route = [route];
185
+
186
+ if (!nav.group) {
187
+ generatedNavItems.push(nav.navItem);
188
+ return;
189
+ }
190
+
191
+ if (!generatedNavGroups.includes(nav.group)) {
192
+ generatedNavGroups.push(nav.group);
193
+ }
194
+
195
+ let groupItem = generatedNavItems.find((item) => item.label === nav.group);
196
+
197
+ if (!groupItem) {
198
+ groupItem = { label: nav.group, children: [] };
199
+ generatedNavItems.push(groupItem);
200
+ }
201
+
202
+ groupItem.children = groupItem.children || [];
203
+ groupItem.children.push(nav.navItem);
204
+
205
+ generatedNavItems.sort((a, b) => (a.label > b.label ? -1 : 1));
206
+ }
207
+
208
+ /**
209
+ * Recursive function to extract nested field names from a config.
210
+ * @param elements - The elements to extract field names from.
211
+ * @returns - An array of field names.
212
+ */
213
+ export function extractFieldNames(elements: ConfigElement[]): string[] {
214
+ const fields: string[] = [];
215
+ for (const element of elements) {
216
+ if (element.type === 'section') {
217
+ fields.push(...extractFieldNames(element.children));
218
+ } else if (element.type === 'field') {
219
+ fields.push(element.name as string);
220
+ }
221
+ }
222
+ return fields;
223
+ }
224
+
225
+ /**
226
+ * Checks if a string is a valid UUID (versions 1-5) according to RFC 4122.
227
+ *
228
+ * @param str - The string to validate.
229
+ * @returns `true` if the string is a valid UUID, otherwise `false`.
230
+ */
231
+ export function isUUID(str: string): boolean {
232
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
233
+ return uuidRegex.test(str);
234
+ }
235
+
236
+ /**
237
+ * Coerces metadata types in a record according to the metadata.
238
+ *
239
+ * @param record - The record to coerce.
240
+ * @param meta - The metadata (resource or method).
241
+ * @returns The coerced record.
242
+ */
243
+ export function metadataTypeCoercion(record: RecordData, meta: ResourceMeta): RecordData;
244
+ export function metadataTypeCoercion(record: RPCRecordData, meta: MethodMeta): RPCRecordData;
245
+ export function metadataTypeCoercion(
246
+ record: RecordData | RPCRecordData,
247
+ meta: ResourceMeta | MethodMeta,
248
+ ): RecordData | RPCRecordData {
249
+ if (!meta?.fields) return record;
250
+
251
+ const displayTypeByField = new Map(meta.fields.map((f) => [f.fieldName, f.displayType]));
252
+
253
+ for (const [key, value] of Object.entries(record)) {
254
+ const displayType = displayTypeByField.get(key);
255
+ if (displayType === 'civildate' && value && isValid(value)) {
256
+ const date = value as Date;
257
+ record[key] = format(date, 'yyyy-MM-dd');
258
+ }
259
+ }
260
+
261
+ return record;
262
+ }
@@ -0,0 +1,6 @@
1
+ import { ValidatorFn } from '@angular/forms';
2
+ import { ResourceValidatorFn } from '@cccteam/ccc-lib/src/types';
3
+
4
+ export function createResourceValidator(validator: ValidatorFn): ResourceValidatorFn {
5
+ return validator as ResourceValidatorFn;
6
+ }
@@ -1,23 +1,44 @@
1
+ /*
2
+ * Public API Surface of ccc-lib
3
+ */
4
+
5
+ // Auth service
6
+ export * from '@cccteam/ccc-lib/src/auth-service';
7
+
8
+ // UI Core service
9
+ export * from '@cccteam/ccc-lib/src/ui-core-service';
10
+
11
+ // Auth directives
1
12
  export * from '@cccteam/ccc-lib/src/auth-has-permission';
13
+
14
+ // Auth forms
2
15
  export * from '@cccteam/ccc-lib/src/auth-forms';
16
+
17
+ // Auth guards
3
18
  export * from '@cccteam/ccc-lib/src/auth-authentication-guard';
4
19
  export * from '@cccteam/ccc-lib/src/auth-authorization-guard';
5
- export * from '@cccteam/ccc-lib/src/auth-service';
20
+
21
+ // Util
6
22
  export * from '@cccteam/ccc-lib/src/util-request-options';
7
- export * from '@cccteam/ccc-lib/src/auth-state';
23
+
24
+ // Types
8
25
  export * from '@cccteam/ccc-lib/src/types';
26
+
27
+ // Ui components
9
28
  export * from '@cccteam/ccc-lib/src/ui-alert';
10
29
  export * from '@cccteam/ccc-lib/src/ui-sidenav';
30
+
31
+ // Ui interceptor
11
32
  export * from '@cccteam/ccc-lib/src/ui-interceptor';
33
+
34
+ // Ui services
12
35
  export * from '@cccteam/ccc-lib/src/ui-notification-service';
13
- export * from '@cccteam/ccc-lib/src/ui-core-state';
14
36
 
15
- /*
16
- * Public API Surface of ccc-lib
17
- */
18
- // Auth directives
37
+ // Resources
38
+ export * from '@cccteam/ccc-lib/src/ccc-resource';
19
39
 
20
- /**
21
- * Generated bundle index. Do not edit.
22
- */
23
- //# sourceMappingURL=cccteam-ccc-lib.mjs.map
40
+ // Camel case pipe
41
+ export * from '@cccteam/ccc-lib/src/ccc-camel-case-to-title';
42
+
43
+ // Grid
44
+ export * from '@cccteam/ccc-lib/src/ccc-grid';
@@ -0,0 +1 @@
1
+ export * from '@cccteam/ccc-lib/src/types';
@@ -0,0 +1,6 @@
1
+ {
2
+ "$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json",
3
+ "lib": {
4
+ "entryFile": "./index.ts"
5
+ }
6
+ }