@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,71 @@
1
+ <ng-container [formGroup]="form()">
2
+ @if (showField()) {
3
+ @let options = availableEnumOptions();
4
+ @if (editMode() === 'edit') {
5
+ <div class="enumerated-field-container">
6
+ <mat-form-field class="field {{ fieldClass() }}" [subscriptSizing]="'dynamic'">
7
+ <mat-label>{{ fieldConfig().label }}</mat-label>
8
+ @if (fieldConfig().enumeratedConfig.searchable) {
9
+ <input
10
+ #input
11
+ type="text"
12
+ placeholder="Search"
13
+ matInput
14
+ [matAutocomplete]="auto"
15
+ [formControlName]="fieldConfig().name"
16
+ (input)="search(input.value)" />
17
+ <mat-autocomplete
18
+ (optionSelected)="select($event.option.value)"
19
+ #auto="matAutocomplete"
20
+ [displayWith]="getDisplayText">
21
+ @for (option of options; track option.id) {
22
+ <mat-option [value]="option.id">{{ option?.display }}</mat-option>
23
+ }
24
+ </mat-autocomplete>
25
+ } @else {
26
+ <mat-select
27
+ [formControlName]="fieldConfig().name"
28
+ (keydown.enter)="$event.preventDefault()"
29
+ (selectionChange)="select($event.value)"
30
+ [ariaReadOnly]="editMode() === 'view'">
31
+ @for (option of options; track option.id) {
32
+ @if (option?.id === form().get(this.fieldConfig().name)?.value) {
33
+ <mat-option [value]="option?.id" selected>{{ option?.display }}</mat-option>
34
+ } @else {
35
+ <mat-option [value]="option?.id">{{ option?.display }}</mat-option>
36
+ }
37
+ }
38
+ </mat-select>
39
+ }
40
+ </mat-form-field>
41
+ </div>
42
+ } @else {
43
+ <div class="enumerated-field-container readonly-field">
44
+ <mat-form-field class="field {{ fieldClass() }}" [subscriptSizing]="'dynamic'">
45
+ <mat-label>{{ fieldConfig().label }}</mat-label>
46
+ <input
47
+ id="{{ fieldConfig().name }}-readonly-input-text"
48
+ matInput
49
+ (keydown.enter)="$event.preventDefault()"
50
+ [readonly]="true"
51
+ type="text"
52
+ [required]="hasRequiredValidator()"
53
+ [value]="singleEnumDisplayText()" />
54
+ </mat-form-field>
55
+ @if (viewDetails()) {
56
+ <a
57
+ mat-icon-button
58
+ color="accent"
59
+ [matTooltip]="tooltipMessage()"
60
+ matTooltipPosition="below"
61
+ [routerLink]="['/', route(), form().get(this.fieldConfig().name)?.value]">
62
+ <mat-icon>arrow_forward</mat-icon>
63
+ </a>
64
+ }
65
+ </div>
66
+ <input class="display-none" [formControlName]="fieldConfig().name" />
67
+ }
68
+ } @else {
69
+ <input class="display-none" [formControlName]="fieldConfig().name" />
70
+ }
71
+ </ng-container>
@@ -0,0 +1,9 @@
1
+ .enumerated-field-container {
2
+ display: flex;
3
+ flex-direction: row;
4
+ gap: 10px;
5
+ }
6
+
7
+ .display-none {
8
+ display: none;
9
+ }
@@ -0,0 +1,22 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { EnumeratedFieldComponent } from './enumerated-field.component';
4
+
5
+ xdescribe('EnumeratedFieldComponent', () => {
6
+ let component: EnumeratedFieldComponent;
7
+ let fixture: ComponentFixture<EnumeratedFieldComponent>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ imports: [EnumeratedFieldComponent],
12
+ }).compileComponents();
13
+
14
+ fixture = TestBed.createComponent(EnumeratedFieldComponent);
15
+ component = fixture.componentInstance;
16
+ fixture.detectChanges();
17
+ });
18
+
19
+ it('should create', () => {
20
+ expect(component).toBeTruthy();
21
+ });
22
+ });
@@ -0,0 +1,207 @@
1
+ import { ChangeDetectionStrategy, Component, computed, inject, signal, untracked } from '@angular/core';
2
+ import { ReactiveFormsModule, Validators } from '@angular/forms';
3
+ import { MatAutocompleteModule } from '@angular/material/autocomplete';
4
+ import { MatButtonModule } from '@angular/material/button';
5
+ import { MatOptionModule } from '@angular/material/core';
6
+ import { MatDatepickerModule } from '@angular/material/datepicker';
7
+ import { MatFormFieldModule } from '@angular/material/form-field';
8
+ import { MatIconModule } from '@angular/material/icon';
9
+ import { MatInputModule } from '@angular/material/input';
10
+ import { MatSelectModule } from '@angular/material/select';
11
+ import { MatTooltipModule } from '@angular/material/tooltip';
12
+ import { ActivatedRoute, Router, RouterModule } from '@angular/router';
13
+ import { defaultEmptyFieldValue, EnumeratedConfig, FieldElement, Resource } from '@cccteam/ccc-lib/src/types';
14
+ import { concatFunctions, hyphenConcat } from '../../../concat-fns';
15
+ import { BaseInputComponent } from '../../base-field.directive';
16
+
17
+ @Component({
18
+ selector: 'ccc-enumerated-field',
19
+ imports: [
20
+ MatFormFieldModule,
21
+ MatInputModule,
22
+ MatDatepickerModule,
23
+ ReactiveFormsModule,
24
+ MatOptionModule,
25
+ MatSelectModule,
26
+ MatIconModule,
27
+ MatButtonModule,
28
+ MatTooltipModule,
29
+ RouterModule,
30
+ MatAutocompleteModule,
31
+ ],
32
+ templateUrl: './enumerated-field.component.html',
33
+ styleUrl: './enumerated-field.component.scss',
34
+ changeDetection: ChangeDetectionStrategy.OnPush,
35
+ })
36
+ export class EnumeratedFieldComponent extends BaseInputComponent {
37
+ activatedRoute = inject(ActivatedRoute);
38
+ router = inject(Router);
39
+ reloadSignal = signal(false);
40
+
41
+ query = signal('');
42
+
43
+ resource = computed(() => {
44
+ if (this.fieldConfig()?.enumeratedConfig?.overrideResource) {
45
+ return this.fieldConfig()?.enumeratedConfig?.overrideResource;
46
+ }
47
+ return this.fieldMeta()?.enumeratedResource as Resource;
48
+ });
49
+
50
+ tooltipMessage = computed(() => {
51
+ const label = this.fieldConfig().label;
52
+ return 'View ' + label + ' details';
53
+ });
54
+
55
+ route = computed(() => {
56
+ const resource = this.fieldMeta()?.enumeratedResource;
57
+ return this.resourceMeta(resource as Resource)?.route;
58
+ });
59
+
60
+ viewDetails = computed(() => {
61
+ return this.editMode() === 'view' && this.fieldConfig().enumeratedConfig.viewDetails === true;
62
+ });
63
+
64
+ sorts = computed(() => {
65
+ return this.fieldConfig().enumeratedConfig.sorts;
66
+ });
67
+
68
+ singleEnumResourceRef = computed(() => {
69
+ this.editMode();
70
+ if (this.showField() === false) {
71
+ return undefined;
72
+ }
73
+
74
+ const route = this.route();
75
+ const resource = this.resource();
76
+
77
+ this.reloadSignal();
78
+ const fieldValue = this.form().get(this.fieldConfig().name)?.value;
79
+
80
+ if (fieldValue && route && resource) {
81
+ return untracked(() => this.cache.registerView(this.route, this.resource, signal(fieldValue)));
82
+ }
83
+ return undefined;
84
+ });
85
+
86
+ enumResourceRef = computed(() => {
87
+ if (this.showField() === false) return undefined;
88
+ const enumeratedMeta = this.resourceMeta(this.resource() as Resource);
89
+ const filter = this.fieldConfig().enumeratedConfig?.filter?.(this.relatedData()) || '';
90
+ return this.cache.registerList(
91
+ signal(enumeratedMeta.route),
92
+ this.resource,
93
+ signal(filter),
94
+ signal([]),
95
+ this.query,
96
+ this.sorts,
97
+ );
98
+ });
99
+
100
+ singleEnumDisplayText = computed(() => {
101
+ const showField = this.showField();
102
+ if (showField === false) {
103
+ return undefined;
104
+ }
105
+
106
+ const form = this.form();
107
+ const fieldConfig = this.fieldConfig();
108
+ const singleEnumResourceRef = this.singleEnumResourceRef();
109
+
110
+ if (form === undefined || fieldConfig === undefined || singleEnumResourceRef === undefined) {
111
+ return defaultEmptyFieldValue;
112
+ }
113
+
114
+ const record = singleEnumResourceRef.value();
115
+ if (record === undefined) {
116
+ return defaultEmptyFieldValue;
117
+ }
118
+
119
+ const enumeratedValues = this.toEnumerated(record as Record<string, string>, fieldConfig);
120
+ return enumeratedValues.display;
121
+ });
122
+
123
+ hasRequiredValidator = computed(() => {
124
+ const form = this.form();
125
+ const fieldConfig = this.fieldConfig();
126
+
127
+ if (form === undefined || fieldConfig === undefined) {
128
+ return false;
129
+ }
130
+
131
+ const control = form.get(fieldConfig.name);
132
+ if (!control) {
133
+ return false;
134
+ }
135
+
136
+ return control.hasValidator(Validators.required);
137
+ });
138
+
139
+ singleEnumValue = computed(() => {
140
+ if (this.showField() === false) return undefined;
141
+ const currentValue = this.form().get(this.fieldConfig().name)?.value;
142
+ const record = this.singleEnumResourceRef()?.value();
143
+ if (!currentValue) return [];
144
+ if (!record) return [];
145
+ return [this.toEnumerated(record as Record<string, string>, this.fieldConfig())];
146
+ });
147
+
148
+ listEnumValues = computed(() => {
149
+ if (this.showField() === false) return [];
150
+ const currentValue = this.singleEnumValue();
151
+ const records = this.enumResourceRef()?.value();
152
+ if (!records || !records.length) return currentValue || [];
153
+ return records.map((record) => this.toEnumerated(record as Record<string, string>, this.fieldConfig()));
154
+ });
155
+
156
+ availableEnumOptions = computed(() => {
157
+ if (this.editMode() === 'edit') {
158
+ return this.listEnumValues();
159
+ }
160
+ return this.singleEnumValue();
161
+ });
162
+
163
+ toEnumerated(resource: Record<string, string>, element: FieldElement): { id: string; display: string } {
164
+ const enumeratedConfig = element.enumeratedConfig as EnumeratedConfig;
165
+ const displayFields =
166
+ this.editMode() === 'edit' && enumeratedConfig.listDisplay.length > 0
167
+ ? enumeratedConfig.listDisplay
168
+ : enumeratedConfig.viewDisplay;
169
+ const concat =
170
+ this.editMode() === 'edit'
171
+ ? enumeratedConfig.listConcatFn || enumeratedConfig.viewConcatFn
172
+ : enumeratedConfig.viewConcatFn;
173
+
174
+ const concatFunction = concatFunctions[concat] || hyphenConcat;
175
+
176
+ return {
177
+ id: resource['id'] ?? '',
178
+ display: concatFunction(resource, ...displayFields),
179
+ };
180
+ }
181
+
182
+ getDisplayText = (value: string | Record<string, string> | null): string => {
183
+ if (!value) return '';
184
+
185
+ if (typeof value === 'string') {
186
+ const option = this.availableEnumOptions()?.find((o) => o.id === value);
187
+ return option ? option['display'] || this.formatDisplay(option) : '';
188
+ }
189
+
190
+ return value['display'] || this.formatDisplay(value);
191
+ };
192
+
193
+ private formatDisplay(resource: Record<string, string>): string {
194
+ return this.toEnumerated(resource, this.fieldConfig()).display;
195
+ }
196
+
197
+ search(value: string): void {
198
+ this.query.set(value);
199
+ }
200
+
201
+ select(value: string): void {
202
+ this.form().patchValue({ name: this.fieldConfig().name, value });
203
+ this.form().markAsDirty();
204
+ this.form().markAsTouched();
205
+ this.reloadSignal.update((prev) => !prev);
206
+ }
207
+ }
@@ -0,0 +1,38 @@
1
+ <ng-container [formGroup]="form()">
2
+ @if (showField()) {
3
+ @let options = availableEnumOptions();
4
+ @if (editMode() === 'edit') {
5
+ <mat-form-field class="field {{ fieldClass() }}" [subscriptSizing]="'dynamic'">
6
+ <mat-label>{{ fieldConfig().label }}</mat-label>
7
+ <mat-select
8
+ [formControlName]="fieldConfig().name"
9
+ (keydown.enter)="$event.preventDefault()"
10
+ [canSelectNullableOptions]="true"
11
+ [ariaReadOnly]="editMode() === 'view'">
12
+ @for (option of options; track option[0]) {
13
+ @if (option[0] === currentValue()) {
14
+ <mat-option [value]="option[0]" selected>{{ option[1] }}</mat-option>
15
+ } @else {
16
+ <mat-option [value]="option[0]">{{ option[1] }}</mat-option>
17
+ }
18
+ }
19
+ </mat-select>
20
+ </mat-form-field>
21
+ } @else {
22
+ <div class="readonly-field">
23
+ <mat-form-field class="field {{ fieldClass() }}" [subscriptSizing]="'dynamic'">
24
+ <mat-label>{{ fieldConfig().label }}</mat-label>
25
+ <input
26
+ matInput
27
+ (keydown.enter)="$event.preventDefault()"
28
+ [readonly]="true"
29
+ type="text"
30
+ [required]="hasRequiredValidator()"
31
+ [value]="currentDisplayText()" />
32
+ </mat-form-field>
33
+ </div>
34
+ }
35
+ } @else {
36
+ <input class="display-none" [formControlName]="fieldConfig().name" />
37
+ }
38
+ </ng-container>
@@ -0,0 +1,22 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { NullBooleanFieldComponent } from './nullboolean-field.component';
4
+
5
+ xdescribe('NullBooleanFieldComponent', () => {
6
+ let component: NullBooleanFieldComponent;
7
+ let fixture: ComponentFixture<NullBooleanFieldComponent>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ imports: [NullBooleanFieldComponent],
12
+ }).compileComponents();
13
+
14
+ fixture = TestBed.createComponent(NullBooleanFieldComponent);
15
+ component = fixture.componentInstance;
16
+ fixture.detectChanges();
17
+ });
18
+
19
+ it('should create', () => {
20
+ expect(component).toBeTruthy();
21
+ });
22
+ });
@@ -0,0 +1,87 @@
1
+ import { ChangeDetectionStrategy, Component, computed } from '@angular/core';
2
+ import { ReactiveFormsModule, Validators } from '@angular/forms';
3
+ import { MatAutocompleteModule } from '@angular/material/autocomplete';
4
+ import { MatButtonModule } from '@angular/material/button';
5
+ import { MatOptionModule } from '@angular/material/core';
6
+ import { MatDatepickerModule } from '@angular/material/datepicker';
7
+ import { MatFormFieldModule } from '@angular/material/form-field';
8
+ import { MatIconModule } from '@angular/material/icon';
9
+ import { MatInputModule } from '@angular/material/input';
10
+ import { MatSelectModule } from '@angular/material/select';
11
+ import { MatTooltipModule } from '@angular/material/tooltip';
12
+ import { RouterModule } from '@angular/router';
13
+ import { NullBoolean } from '@cccteam/ccc-lib/src/types';
14
+ import { BaseInputComponent } from '../../base-field.directive';
15
+
16
+ @Component({
17
+ selector: 'ccc-nullboolean-field',
18
+ imports: [
19
+ MatFormFieldModule,
20
+ MatInputModule,
21
+ MatDatepickerModule,
22
+ ReactiveFormsModule,
23
+ MatOptionModule,
24
+ MatSelectModule,
25
+ MatIconModule,
26
+ MatButtonModule,
27
+ MatTooltipModule,
28
+ RouterModule,
29
+ MatAutocompleteModule,
30
+ ],
31
+ templateUrl: './nullboolean-field.component.html',
32
+ styleUrl: './nullboolean-field.component.scss',
33
+ changeDetection: ChangeDetectionStrategy.OnPush,
34
+ })
35
+ export class NullBooleanFieldComponent extends BaseInputComponent {
36
+ availableEnumOptions = computed(() => {
37
+ const fieldConfig = this.fieldConfig();
38
+ if (fieldConfig === undefined) {
39
+ return;
40
+ }
41
+ const valueMap = new Map<NullBoolean, string>();
42
+
43
+ Object.values(fieldConfig.nullBooleanConfig.displayValues).forEach((value) => {
44
+ valueMap.set(value.value, value.label);
45
+ });
46
+
47
+ return valueMap;
48
+ });
49
+
50
+ currentValue = computed(() => {
51
+ const form = this.form();
52
+ const fieldConfig = this.fieldConfig();
53
+
54
+ if (form === undefined || fieldConfig === undefined) {
55
+ return;
56
+ }
57
+
58
+ return form.get(fieldConfig.name)?.value;
59
+ });
60
+
61
+ currentDisplayText = computed(() => {
62
+ const value = this.currentValue();
63
+ const availableOptions = this.availableEnumOptions();
64
+
65
+ if (availableOptions === undefined || value === undefined) {
66
+ return;
67
+ }
68
+
69
+ return availableOptions.get(value);
70
+ });
71
+
72
+ hasRequiredValidator = computed(() => {
73
+ const form = this.form();
74
+ const fieldConfig = this.fieldConfig();
75
+
76
+ if (form === undefined || fieldConfig === undefined) {
77
+ return false;
78
+ }
79
+
80
+ const control = form.get(fieldConfig.name);
81
+ if (control === null) {
82
+ return false;
83
+ }
84
+
85
+ return control.hasValidator(Validators.required);
86
+ });
87
+ }
@@ -0,0 +1,23 @@
1
+ @if (showField()) {
2
+ <div [class.readonly-field]="editMode() === 'view'">
3
+ <mat-form-field
4
+ class="field {{ fieldClass() }}"
5
+ [formGroup]="form()"
6
+ [subscriptSizing]="'dynamic'"
7
+ [floatLabel]="floatLabel()">
8
+ <mat-label>{{ fieldConfig().label }}</mat-label>
9
+ <span matTextPrefix class="prefix">{{ prefixString() }}</span>
10
+ <span matTextSuffix class="suffix">{{ suffixString() }}</span>
11
+ <input
12
+ matInput
13
+ (keydown.enter)="$event.preventDefault()"
14
+ [readonly]="editMode() === 'view'"
15
+ type="number"
16
+ [formControlName]="fieldConfig().name" />
17
+ </mat-form-field>
18
+ </div>
19
+ } @else {
20
+ <ng-container [formGroup]="form()">
21
+ <input type="hidden" [formControlName]="fieldConfig().name" />
22
+ </ng-container>
23
+ }
@@ -0,0 +1,6 @@
1
+ .prefix,
2
+ .suffix {
3
+ font-style: italic;
4
+ color: gray;
5
+ padding-right: 5px;
6
+ }
@@ -0,0 +1,22 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { NumberFieldComponent } from './number-field.component';
4
+
5
+ xdescribe('NumberFieldComponent', () => {
6
+ let component: NumberFieldComponent;
7
+ let fixture: ComponentFixture<NumberFieldComponent>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ imports: [NumberFieldComponent],
12
+ }).compileComponents();
13
+
14
+ fixture = TestBed.createComponent(NumberFieldComponent);
15
+ component = fixture.componentInstance;
16
+ fixture.detectChanges();
17
+ });
18
+
19
+ it('should create', () => {
20
+ expect(component).toBeTruthy();
21
+ });
22
+ });
@@ -0,0 +1,14 @@
1
+ import { Component } from '@angular/core';
2
+ import { ReactiveFormsModule } from '@angular/forms';
3
+ import { MatDatepickerModule } from '@angular/material/datepicker';
4
+ import { MatFormFieldModule } from '@angular/material/form-field';
5
+ import { MatInputModule } from '@angular/material/input';
6
+ import { BaseInputComponent } from '../../base-field.directive';
7
+
8
+ @Component({
9
+ selector: 'ccc-number-field',
10
+ imports: [MatFormFieldModule, MatInputModule, MatDatepickerModule, ReactiveFormsModule],
11
+ templateUrl: './number-field.component.html',
12
+ styleUrl: './number-field.component.scss',
13
+ })
14
+ export class NumberFieldComponent extends BaseInputComponent {}
@@ -0,0 +1,29 @@
1
+ @if (showField()) {
2
+ <div [class.readonly-field]="editMode() === 'view'">
3
+ <mat-form-field
4
+ class="field {{ fieldClass() }}"
5
+ [formGroup]="form()"
6
+ [subscriptSizing]="'dynamic'"
7
+ [floatLabel]="floatLabel()">
8
+ <mat-label>{{ fieldConfig().label }}</mat-label>
9
+ <span matTextPrefix class="prefix">{{ prefixString() }}</span>
10
+ <span matTextSuffix class="suffix">{{ suffixString() }}</span>
11
+ <input
12
+ matInput
13
+ (keydown.enter)="$event.preventDefault()"
14
+ [readonly]="editMode() === 'view'"
15
+ type="text"
16
+ [formControlName]="fieldConfig().name"
17
+ [class.dirty-input]="form().get(fieldConfig().name)?.dirty" />
18
+ @if (editMode() === 'edit' && form().get(fieldConfig().name)?.dirty) {
19
+ <button matSuffix mat-icon-button (click)="reset()" tabindex="-1">
20
+ <mat-icon>refresh</mat-icon>
21
+ </button>
22
+ }
23
+ </mat-form-field>
24
+ </div>
25
+ } @else {
26
+ <ng-container [formGroup]="form()">
27
+ <input type="hidden" [formControlName]="fieldConfig().name" />
28
+ </ng-container>
29
+ }
@@ -0,0 +1,6 @@
1
+ .prefix,
2
+ .suffix {
3
+ font-style: italic;
4
+ color: gray;
5
+ padding-right: 5px;
6
+ }
@@ -0,0 +1,22 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { TextFieldComponent } from './text-field.component';
4
+
5
+ xdescribe('TextFieldComponent', () => {
6
+ let component: TextFieldComponent;
7
+ let fixture: ComponentFixture<TextFieldComponent>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ imports: [TextFieldComponent],
12
+ }).compileComponents();
13
+
14
+ fixture = TestBed.createComponent(TextFieldComponent);
15
+ component = fixture.componentInstance;
16
+ fixture.detectChanges();
17
+ });
18
+
19
+ it('should create', () => {
20
+ expect(component).toBeTruthy();
21
+ });
22
+ });
@@ -0,0 +1,23 @@
1
+ import { Component } from '@angular/core';
2
+ import { ReactiveFormsModule } from '@angular/forms';
3
+ import { MatButtonModule } from '@angular/material/button';
4
+ import { MatDatepickerModule } from '@angular/material/datepicker';
5
+ import { MatFormFieldModule } from '@angular/material/form-field';
6
+ import { MatIconModule } from '@angular/material/icon';
7
+ import { MatInputModule } from '@angular/material/input';
8
+ import { BaseInputComponent } from '../../base-field.directive';
9
+
10
+ @Component({
11
+ selector: 'ccc-text-field',
12
+ imports: [
13
+ MatFormFieldModule,
14
+ MatInputModule,
15
+ MatDatepickerModule,
16
+ ReactiveFormsModule,
17
+ MatIconModule,
18
+ MatButtonModule,
19
+ ],
20
+ templateUrl: './text-field.component.html',
21
+ styleUrl: './text-field.component.scss',
22
+ })
23
+ export class TextFieldComponent extends BaseInputComponent {}