@tinya-code/gas-admin-ui 0.2.0-alpha.3 → 0.2.0-alpha.4
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.
- package/README.md +186 -2
- package/fesm2022/{tinya-code-gas-admin-ui-calendar-view-DkYfP4n9.mjs → tinya-code-gas-admin-ui-calendar-view-ZTYs-r49.mjs} +12 -7
- package/fesm2022/tinya-code-gas-admin-ui-calendar-view-ZTYs-r49.mjs.map +1 -0
- package/fesm2022/{tinya-code-gas-admin-ui-condition-evaluator-C1tjeb6D.mjs → tinya-code-gas-admin-ui-condition-evaluator-CHzJuOx2.mjs} +2 -2
- package/fesm2022/{tinya-code-gas-admin-ui-condition-evaluator-C1tjeb6D.mjs.map → tinya-code-gas-admin-ui-condition-evaluator-CHzJuOx2.mjs.map} +1 -1
- package/fesm2022/{tinya-code-gas-admin-ui-data-source.service-BUoA9kBh.mjs → tinya-code-gas-admin-ui-data-source.service-Cw1rdrWZ.mjs} +2 -2
- package/fesm2022/{tinya-code-gas-admin-ui-data-source.service-BUoA9kBh.mjs.map → tinya-code-gas-admin-ui-data-source.service-Cw1rdrWZ.mjs.map} +1 -1
- package/fesm2022/{tinya-code-gas-admin-ui-error-mapper.service-BDA1dL5V.mjs → tinya-code-gas-admin-ui-error-mapper.service-BQ--gSnT.mjs} +2 -2
- package/fesm2022/{tinya-code-gas-admin-ui-error-mapper.service-BDA1dL5V.mjs.map → tinya-code-gas-admin-ui-error-mapper.service-BQ--gSnT.mjs.map} +1 -1
- package/fesm2022/{tinya-code-gas-admin-ui-file-DhLi3eO0.mjs → tinya-code-gas-admin-ui-file-JstsF8C5.mjs} +3 -3
- package/fesm2022/{tinya-code-gas-admin-ui-file-DhLi3eO0.mjs.map → tinya-code-gas-admin-ui-file-JstsF8C5.mjs.map} +1 -1
- package/fesm2022/{tinya-code-gas-admin-ui-form-view-CjC-GOQl.mjs → tinya-code-gas-admin-ui-form-view-BcjgVVKC.mjs} +9 -9
- package/fesm2022/{tinya-code-gas-admin-ui-form-view-CjC-GOQl.mjs.map → tinya-code-gas-admin-ui-form-view-BcjgVVKC.mjs.map} +1 -1
- package/fesm2022/{tinya-code-gas-admin-ui-image-D9-mpBhA.mjs → tinya-code-gas-admin-ui-image-CnGes7gs.mjs} +3 -3
- package/fesm2022/{tinya-code-gas-admin-ui-image-D9-mpBhA.mjs.map → tinya-code-gas-admin-ui-image-CnGes7gs.mjs.map} +1 -1
- package/fesm2022/{tinya-code-gas-admin-ui-kanban-view-B4u2ibPb.mjs → tinya-code-gas-admin-ui-kanban-view-QK7OSFoV.mjs} +4 -4
- package/fesm2022/{tinya-code-gas-admin-ui-kanban-view-B4u2ibPb.mjs.map → tinya-code-gas-admin-ui-kanban-view-QK7OSFoV.mjs.map} +1 -1
- package/fesm2022/{tinya-code-gas-admin-ui-list-CQpL6vyE.mjs → tinya-code-gas-admin-ui-list-CiNQ3DMg.mjs} +2 -2
- package/fesm2022/{tinya-code-gas-admin-ui-list-CQpL6vyE.mjs.map → tinya-code-gas-admin-ui-list-CiNQ3DMg.mjs.map} +1 -1
- package/fesm2022/{tinya-code-gas-admin-ui-multiselect-CTQPGBOK.mjs → tinya-code-gas-admin-ui-multiselect-BiKb7192.mjs} +3 -3
- package/fesm2022/{tinya-code-gas-admin-ui-multiselect-CTQPGBOK.mjs.map → tinya-code-gas-admin-ui-multiselect-BiKb7192.mjs.map} +1 -1
- package/fesm2022/{tinya-code-gas-admin-ui-relation-lr_XG6jt.mjs → tinya-code-gas-admin-ui-relation-CvxkEGHs.mjs} +3 -3
- package/fesm2022/{tinya-code-gas-admin-ui-relation-lr_XG6jt.mjs.map → tinya-code-gas-admin-ui-relation-CvxkEGHs.mjs.map} +1 -1
- package/fesm2022/{tinya-code-gas-admin-ui-render-registry-CcIgBsSl.mjs → tinya-code-gas-admin-ui-render-registry-B7mmgFSW.mjs} +10 -10
- package/fesm2022/{tinya-code-gas-admin-ui-render-registry-CcIgBsSl.mjs.map → tinya-code-gas-admin-ui-render-registry-B7mmgFSW.mjs.map} +1 -1
- package/fesm2022/{tinya-code-gas-admin-ui-schema-catalog-DU_6_CtP.mjs → tinya-code-gas-admin-ui-schema-catalog-CsB26qUM.mjs} +190 -61
- package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-CsB26qUM.mjs.map +1 -0
- package/fesm2022/{tinya-code-gas-admin-ui-seed-Bn6tl-yM.mjs → tinya-code-gas-admin-ui-seed-CJEt__kZ.mjs} +2 -2
- package/fesm2022/{tinya-code-gas-admin-ui-seed-Bn6tl-yM.mjs.map → tinya-code-gas-admin-ui-seed-CJEt__kZ.mjs.map} +1 -1
- package/fesm2022/{tinya-code-gas-admin-ui-select-BwefyMVx.mjs → tinya-code-gas-admin-ui-select-DPKRS6Gn.mjs} +3 -3
- package/fesm2022/{tinya-code-gas-admin-ui-select-BwefyMVx.mjs.map → tinya-code-gas-admin-ui-select-DPKRS6Gn.mjs.map} +1 -1
- package/fesm2022/{tinya-code-gas-admin-ui-upload.service-DRmKFrSH.mjs → tinya-code-gas-admin-ui-upload.service-BNvH04S3.mjs} +2 -2
- package/fesm2022/{tinya-code-gas-admin-ui-upload.service-DRmKFrSH.mjs.map → tinya-code-gas-admin-ui-upload.service-BNvH04S3.mjs.map} +1 -1
- package/fesm2022/tinya-code-gas-admin-ui.mjs +51 -53
- package/fesm2022/tinya-code-gas-admin-ui.mjs.map +1 -1
- package/package.json +1 -1
- package/types/tinya-code-gas-admin-ui.d.ts +190 -12
- package/fesm2022/tinya-code-gas-admin-ui-calendar-view-DkYfP4n9.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-DU_6_CtP.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -738,6 +738,190 @@ src/app/schemas/
|
|
|
738
738
|
|
|
739
739
|
`scaffold` fails with exit 1 if the target folder already contains `*.ts` files — use `--force` to regenerate.
|
|
740
740
|
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
## Response unwrapping and custom endpoints (engine flexibility)
|
|
744
|
+
|
|
745
|
+
### Response unwrapping (opt-in)
|
|
746
|
+
|
|
747
|
+
By default the engine does not assume any envelope: a `list` accepts a plain array or `{ items, ... }`, and `get`/`create`/`update` return the raw body. To unwrap a nested envelope (e.g. `{ data, meta }`), declare `responseUnwrap` on the resource's `endpoint`:
|
|
748
|
+
|
|
749
|
+
```ts
|
|
750
|
+
endpoint: {
|
|
751
|
+
list: '/admin/products',
|
|
752
|
+
get: '/admin/products/{key}',
|
|
753
|
+
responseUnwrap: { dataKey: 'data', metaKey: 'meta' }, // applies to all ops by default
|
|
754
|
+
},
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
- `dataKey`: key holding the real payload (`data`, `items`, `result`, …).
|
|
758
|
+
- `metaKey`: optional key with metadata (pagination, totals). Read for renewed tokens; not returned to the consumer.
|
|
759
|
+
- `apply`: optional `'list' | 'get' | 'create' | 'update' | 'remove' | 'all'` (default `'all'`).
|
|
760
|
+
|
|
761
|
+
Without the declaration, behavior is unchanged.
|
|
762
|
+
|
|
763
|
+
### Custom endpoints
|
|
764
|
+
|
|
765
|
+
Declare extra endpoints beyond the five standard CRUD operations on the resource:
|
|
766
|
+
|
|
767
|
+
```ts
|
|
768
|
+
import type { ResourceSchema } from '@tinya-code/gas-admin-ui';
|
|
769
|
+
|
|
770
|
+
export const productsSchema: ResourceSchema = {
|
|
771
|
+
// ...
|
|
772
|
+
customEndpoints: {
|
|
773
|
+
recalcRanks: {
|
|
774
|
+
name: 'recalcRanks',
|
|
775
|
+
method: 'POST',
|
|
776
|
+
path: '/admin/products/{key}/ranks/recalculate',
|
|
777
|
+
payloadMode: 'json', // default
|
|
778
|
+
},
|
|
779
|
+
uploadImage: {
|
|
780
|
+
name: 'uploadImage',
|
|
781
|
+
method: 'POST',
|
|
782
|
+
path: '/admin/products/{key}/image',
|
|
783
|
+
payloadMode: 'multipart',
|
|
784
|
+
},
|
|
785
|
+
},
|
|
786
|
+
};
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
Invoke them programmatically through `ApiService`:
|
|
790
|
+
|
|
791
|
+
```ts
|
|
792
|
+
const api = inject(ApiService);
|
|
793
|
+
api.invokeCustomEndpoint<{ ok: boolean }>(schema.customEndpoints!.recalcRanks, { value: 5 }, undefined, 'prod-1')
|
|
794
|
+
.subscribe((res) => console.log(res));
|
|
795
|
+
|
|
796
|
+
const fd = new FormData();
|
|
797
|
+
fd.append('file', file);
|
|
798
|
+
api.invokeCustomEndpoint<{ url: string }>(schema.customEndpoints!.uploadImage, fd, undefined, 'prod-1')
|
|
799
|
+
.subscribe((res) => console.log(res.url));
|
|
800
|
+
```
|
|
801
|
+
|
|
802
|
+
- Any HTTP method (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`) is supported.
|
|
803
|
+
- `payloadMode: 'multipart'` (or passing a `FormData`) sends the body without `Content-Type: application/json`, so the browser sets the multipart boundary.
|
|
804
|
+
- A per-endpoint `responseUnwrap` overrides the resource-level one.
|
|
805
|
+
|
|
806
|
+
### Global default unwrap
|
|
807
|
+
When every resource wraps responses the same way, declare it once in `provideGasAdmin` instead of repeating `responseUnwrap` per resource:
|
|
808
|
+
|
|
809
|
+
```ts
|
|
810
|
+
provideGasAdmin({
|
|
811
|
+
schemas,
|
|
812
|
+
defaultResponseUnwrap: { dataKey: 'data', metaKey: 'meta' },
|
|
813
|
+
});
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
Merge rule: the per-resource declaration wins on every key it declares; the global default fills in the rest. Resources that declare nothing inherit the global default. No `defaultResponseUnwrap` ⇒ only per-resource declarations apply (unchanged behavior).
|
|
817
|
+
|
|
818
|
+
```ts
|
|
819
|
+
// global: { dataKey: 'payload' }
|
|
820
|
+
// resource:{ dataKey: 'data' } ⇒ effective: { dataKey: 'data' } (resource wins)
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
### Pagination — `listPage()`
|
|
824
|
+
|
|
825
|
+
`list()` still returns `T[]` and discards the cursor. To read the pagination metadata the backend already sends (`{ items, nextCursor, total }` or inside `meta`), opt in with `listPage()`:
|
|
826
|
+
|
|
827
|
+
```ts
|
|
828
|
+
const api = inject(ApiService);
|
|
829
|
+
|
|
830
|
+
// First page
|
|
831
|
+
const page = await firstValueFrom(api.listPage<Product>(productsSchema.endpoint));
|
|
832
|
+
page.items; // Product[]
|
|
833
|
+
page.nextCursor; // string | null (null on the last page)
|
|
834
|
+
page.total; // number (0 when the backend omits it)
|
|
835
|
+
page.hasMore; // boolean
|
|
836
|
+
|
|
837
|
+
// Next page: pass the cursor back as a normal payload
|
|
838
|
+
const next = await firstValueFrom(
|
|
839
|
+
api.listPage<Product>(productsSchema.endpoint, { cursor: page.nextCursor, limit: 20 }),
|
|
840
|
+
);
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
| Field | Source | Fallback |
|
|
844
|
+
|-------|--------|----------|
|
|
845
|
+
| `items` | Same unwrapping rules as `list()` (`dataKey` or `{ items }`) | — |
|
|
846
|
+
| `nextCursor` | `pagination.cursorKey` or `meta.nextCursor` | auto-detect `nextCursor`; empty/absent ⇒ `null` |
|
|
847
|
+
| `total` | `pagination.totalKey` or `meta.total` | auto-detect `total`; absent ⇒ `0` |
|
|
848
|
+
| `hasMore` | `pagination.hasMoreKey` (boolean wins) | derived: cursor present ⇒ `true`, else `false` |
|
|
849
|
+
|
|
850
|
+
Custom keys are declared on the resource's unwrap:
|
|
851
|
+
|
|
852
|
+
```ts
|
|
853
|
+
endpoint: {
|
|
854
|
+
list: '/admin/products',
|
|
855
|
+
responseUnwrap: {
|
|
856
|
+
dataKey: 'results',
|
|
857
|
+
metaKey: 'meta',
|
|
858
|
+
pagination: { cursorKey: 'after', totalKey: 'count', hasMoreKey: 'remaining' },
|
|
859
|
+
},
|
|
860
|
+
},
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
Rules:
|
|
864
|
+
|
|
865
|
+
- **Opt-in.** Without `listPage()`, nothing changes: `list()` keeps its `T[]` contract.
|
|
866
|
+
- **Auto-detection.** When no keys are declared, the engine reads the common ones (`nextCursor`, `total`, `hasMore`) from `meta` (if `metaKey` is declared and present) or from the body root.
|
|
867
|
+
- **`hasMore` derivation.** An explicit boolean flag wins; otherwise the cursor's presence decides it. No cursor ⇒ `hasMore: false`.
|
|
868
|
+
- **Payloads.** `cursor`/`limit` (or any pagination params) travel as the normal request payload — query params on REST `GET`, `payload` field in the GAS envelope.
|
|
869
|
+
|
|
870
|
+
### Migration notes
|
|
871
|
+
|
|
872
|
+
Existing schemas keep working unchanged: no `responseUnwrap`, no `customEndpoints`, no `defaultResponseUnwrap` and no `listPage()` means the engine behaves exactly as before.
|
|
873
|
+
|
|
874
|
+
One deliberate change: a `list` whose body is neither an array nor an object (e.g. `null` or a plain string) now **throws a descriptive error** instead of silently returning `[]`. An object without recognized keys still returns `[]` (unchanged), so only a broken list contract surfaces.
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
### Read-only resources
|
|
878
|
+
|
|
879
|
+
A resource that only displays data should not offer create/edit/delete. Declare
|
|
880
|
+
it as read-only in **either** of these ways — the engine honors both:
|
|
881
|
+
|
|
882
|
+
1. **Declare `permissions` (explicit, legacy-friendly):**
|
|
883
|
+
```ts
|
|
884
|
+
export const dashboardSchema: ResourceSchema = {
|
|
885
|
+
// ...
|
|
886
|
+
permissions: { create: false, update: false, remove: false },
|
|
887
|
+
};
|
|
888
|
+
```
|
|
889
|
+
2. **Omit the write endpoints (inferred):** if the schema declares an
|
|
890
|
+
`endpoint` with at least one entry but no `create`/`update`/`remove`, the
|
|
891
|
+
engine treats those operations as unavailable — the "Create" button, the
|
|
892
|
+
edit form and the delete action are hidden, and boolean columns render as
|
|
893
|
+
badges instead of editable toggles. A schema with no `endpoint` at all keeps
|
|
894
|
+
the legacy optimistic behavior (buttons shown, backend decides).
|
|
895
|
+
|
|
896
|
+
```ts
|
|
897
|
+
endpoint: {
|
|
898
|
+
get: '/admin/dashboard', // only read — no create/update/remove
|
|
899
|
+
},
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
This is what makes a dashboard or a report resource read-only without
|
|
903
|
+
repeating `permissions`.
|
|
904
|
+
|
|
905
|
+
### Responsive widget layout (`mobileWidth`)
|
|
906
|
+
|
|
907
|
+
Dashboard widgets use a 12-column grid. By default every widget spans the
|
|
908
|
+
full width on mobile (stacked) and `width` columns from the `sm` breakpoint
|
|
909
|
+
up. To control the mobile layout declaratively, set `mobileWidth` (1–12):
|
|
910
|
+
|
|
911
|
+
```ts
|
|
912
|
+
widgets: [
|
|
913
|
+
{ type: 'metric-card', key: 'totalClients', label: 'Total Clients', width: 2, mobileWidth: 6 }, // 2/row on mobile, 2/12 on desktop
|
|
914
|
+
{ type: 'metric-card', key: 'activeClients30d', label: 'Active (30d)', width: 2, mobileWidth: 6 },
|
|
915
|
+
{ type: 'metric-card', key: 'visitsThisMonth', label: 'Visits', width: 4 }, // full width on mobile (default), 4/12 on desktop
|
|
916
|
+
],
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
- `mobileWidth` defaults to `12` (full width, stacked) — existing schemas
|
|
920
|
+
keep their current mobile behavior.
|
|
921
|
+
- `width` keeps controlling `sm`/`lg` (the desktop grid).
|
|
922
|
+
- Both accept any value 1–12; Tailwind classes are emitted as complete
|
|
923
|
+
literals so the JIT picks them up.
|
|
924
|
+
|
|
741
925
|
---
|
|
742
926
|
|
|
743
927
|
## Public API
|
|
@@ -747,13 +931,13 @@ Only what `public-api.ts` exports is stable contract; everything else is private
|
|
|
747
931
|
| Group | Exports |
|
|
748
932
|
| --- | --- |
|
|
749
933
|
| Wiring & catalog | `provideGasAdmin`, `provideFirebaseAdapter`, `GAS_SCHEMAS`, `setSchemaCatalog`, `getSchema`, `allSchemas`, `getAuth`, type `GasAdminConfig` |
|
|
750
|
-
| Tokens & adapters | `API_URL`, `API_DRIVER`, `VALUE_SOURCES`, `FIREBASE_AUTH_ADAPTER`, type `FirebaseAuthAdapter`, type `ValueSources` |
|
|
934
|
+
| Tokens & adapters | `API_URL`, `API_DRIVER`, `DEFAULT_RESPONSE_UNWRAP`, `VALUE_SOURCES`, `FIREBASE_AUTH_ADAPTER`, type `FirebaseAuthAdapter`, type `ValueSources` |
|
|
751
935
|
| Routes & guard | `authGuard`, `shellRoutes`, `resourceRoutes` |
|
|
752
936
|
| Pages | `Login`, `Dashboard`, `ListView`, `FormView`, `ResourcePage` |
|
|
753
937
|
| Components | `Sidebar`, `ComposerHost`, `FieldHost`, `ConfirmDialog`, `Toast` |
|
|
754
938
|
| Services | `ApiService`, `AuthService` (+ `SESSION_TOKEN_KEY`, `SESSION_USER_KEY`), `NotificationService`, `DataSourceService`, `UploadService`, `ErrorMapperService`, `apiErrorInterceptor` |
|
|
755
939
|
| Forms | `buildFormSchema`, `seedModel`, `buildPayload` |
|
|
756
|
-
| Schema models | `ResourceSchema`, `AuthSchema`, `AuthProvider`, `FirebaseAuthConfig`, `ResourceAction`, `ApiError` and the full field/widget/view/layout vocabulary |
|
|
940
|
+
| Schema models | `ResourceSchema`, `AuthSchema`, `AuthProvider`, `FirebaseAuthConfig`, `ResourceAction`, `ApiError`, `PageResult`, `ResponseUnwrap`, `CustomEndpointDefinition`, `HttpMethod` and the full field/widget/view/layout vocabulary |
|
|
757
941
|
| Render registries | `fieldDescriptors`, `columnFormatDescriptors`, `viewDescriptors`, `layoutDescriptors`, `renderKinds` |
|
|
758
942
|
| Custom render | `registerRenderDescriptor`, `unregisterRenderDescriptor`, `provideRenderDescriptors`, `getRenderDescriptor`, `isSupported`, `isRenderSupported`, types `RenderKind`, `ComponentRenderKind` |
|
|
759
943
|
| Composition | `validateComposition`, `MAX_DEPTH`, `SchemaCompositionError` |
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { i as getSchema } from "./tinya-code-gas-admin-ui-schema-catalog-
|
|
1
|
+
import { i as getSchema } from "./tinya-code-gas-admin-ui-schema-catalog-CsB26qUM.mjs";
|
|
2
2
|
import { t as Button } from "./tinya-code-gas-admin-ui-button-BQjNW8KB.mjs";
|
|
3
|
-
import { t as DataSourceService } from "./tinya-code-gas-admin-ui-data-source.service-
|
|
4
|
-
import { t as ErrorMapperService } from "./tinya-code-gas-admin-ui-error-mapper.service-
|
|
3
|
+
import { t as DataSourceService } from "./tinya-code-gas-admin-ui-data-source.service-Cw1rdrWZ.mjs";
|
|
4
|
+
import { t as ErrorMapperService } from "./tinya-code-gas-admin-ui-error-mapper.service-BQ--gSnT.mjs";
|
|
5
5
|
import { t as EmptyState } from "./tinya-code-gas-admin-ui-empty-state-BMc0QtoT.mjs";
|
|
6
6
|
import { t as Skeleton } from "./tinya-code-gas-admin-ui-skeleton-DnWK0tcZ.mjs";
|
|
7
7
|
import * as i0 from "@angular/core";
|
|
8
|
-
import { ChangeDetectionStrategy, Component, computed, effect, inject, signal } from "@angular/core";
|
|
8
|
+
import { ChangeDetectionStrategy, Component, InjectionToken, computed, effect, inject, signal } from "@angular/core";
|
|
9
9
|
import { ActivatedRoute } from "@angular/router";
|
|
10
10
|
import { LucideChevronLeft, LucideChevronRight } from "@lucide/angular";
|
|
11
11
|
import { toSignal } from "@angular/core/rxjs-interop";
|
|
@@ -85,6 +85,10 @@ function todayCivil() {
|
|
|
85
85
|
const day = String(now.getDate()).padStart(2, "0");
|
|
86
86
|
return `${now.getFullYear()}-${month}-${day}`;
|
|
87
87
|
}
|
|
88
|
+
const CALENDAR_TODAY = new InjectionToken("gas.admin.calendarToday", {
|
|
89
|
+
providedIn: "root",
|
|
90
|
+
factory: () => todayCivil
|
|
91
|
+
});
|
|
88
92
|
function addDays(ms, days) {
|
|
89
93
|
return ms + days * DAY_MS;
|
|
90
94
|
}
|
|
@@ -333,7 +337,8 @@ var CalendarView = class CalendarView {
|
|
|
333
337
|
}, ...ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []);
|
|
334
338
|
explicitMode = signal(void 0, ...ngDevMode ? [{ debugName: "explicitMode" }] : /* istanbul ignore next */ []);
|
|
335
339
|
mode = computed(() => this.explicitMode() ?? this.config()?.defaultView ?? "month", ...ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []);
|
|
336
|
-
cursor = signal(
|
|
340
|
+
cursor = signal(inject(CALENDAR_TODAY)(), ...ngDevMode ? [{ debugName: "cursor" }] : /* istanbul ignore next */ []);
|
|
341
|
+
todayFn = inject(CALENDAR_TODAY);
|
|
337
342
|
visibleRange = computed(() => this.config() === void 0 ? null : visibleRangeFor(this.mode(), this.cursor()), ...ngDevMode ? [{ debugName: "visibleRange" }] : /* istanbul ignore next */ []);
|
|
338
343
|
rangeLabel = computed(() => this.config() === void 0 ? "" : rangeLabelFor(this.mode(), this.cursor()), ...ngDevMode ? [{ debugName: "rangeLabel" }] : /* istanbul ignore next */ []);
|
|
339
344
|
status = signal("loading", ...ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []);
|
|
@@ -386,7 +391,7 @@ var CalendarView = class CalendarView {
|
|
|
386
391
|
this.cursor.set(shiftCursor(this.mode(), this.cursor(), 1));
|
|
387
392
|
}
|
|
388
393
|
today() {
|
|
389
|
-
this.cursor.set(
|
|
394
|
+
this.cursor.set(this.todayFn());
|
|
390
395
|
}
|
|
391
396
|
setMode(mode) {
|
|
392
397
|
this.explicitMode.set(mode);
|
|
@@ -1019,4 +1024,4 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
1019
1024
|
});
|
|
1020
1025
|
export { CalendarView };
|
|
1021
1026
|
|
|
1022
|
-
//# sourceMappingURL=tinya-code-gas-admin-ui-calendar-view-
|
|
1027
|
+
//# sourceMappingURL=tinya-code-gas-admin-ui-calendar-view-ZTYs-r49.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tinya-code-gas-admin-ui-calendar-view-ZTYs-r49.mjs","names":[],"sources":["../../../projects/gas-admin-ui/src/lib/views/calendar-view/calendar-model.ts","../../../projects/gas-admin-ui/src/lib/views/calendar-view/calendar-view.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\nimport type { CalendarViewConfig, FieldSchema } from '../../core/models/schema.model';\nimport type { SourceRow } from '../../core/services/data-source.service';\n\n/**\n * Pure date + event-mapping model for the generic calendar view (R9.8.3).\n *\n * Date model (decided): backend `date` fields are ISO civil dates\n * `YYYY-MM-DD` treated as CALENDAR DAYS — never shifted through the browser\n * timezone. Every parse goes through `Date.UTC` on the literal parts, never\n * `new Date('YYYY-MM-DD')` in local time (that lands on the previous day\n * west of Greenwich).\n */\n\n/** Milliseconds in one civil day — every day-step of the model uses this. */\nexport const DAY_MS = 86_400_000;\n\n/** Visible modes of the calendar. */\nexport type CalendarMode = 'month' | 'week' | 'day' | 'agenda';\n\n/** Inclusive pair of civil days (`YYYY-MM-DD`) that the view paints. */\nexport interface CivilRange {\n desde: string;\n hasta: string;\n}\n\n/** One event painted from a source row. */\nexport interface CalEvent {\n key: string;\n title: string;\n /** True first day (`YYYY-MM-DD`), before any clamp to the visible range. */\n startDay: string;\n /** True last day, inclusive. Equals `startDay` for single-day events. */\n endDay: string;\n color?: string;\n row: SourceRow;\n}\n\n/** A row excluded from the grid because its interval is inconsistent. */\nexport interface CalendarProblem {\n row: SourceRow;\n reason: string;\n}\n\n/** Result of mapping raw rows onto calendar events (R9.8.3). */\nexport interface CalendarMapping {\n events: CalEvent[];\n /** Rows whose `startField` is empty or not a valid civil date. */\n undated: SourceRow[];\n /** Rows whose `endField` makes the interval impossible. */\n invalid: CalendarProblem[];\n}\n\n/** Spanish reasons shown next to problem records (UI copy). */\nexport const REASON_UNDATED = 'Sin fecha de inicio válida';\nexport const REASON_INVALID_END = 'Fecha de fin inválida';\nexport const REASON_END_BEFORE_START = 'El fin es anterior al inicio';\n\nconst MONTHS = [\n 'enero',\n 'febrero',\n 'marzo',\n 'abril',\n 'mayo',\n 'junio',\n 'julio',\n 'agosto',\n 'septiembre',\n 'octubre',\n 'noviembre',\n 'diciembre',\n];\n\nconst MONTHS_SHORT = [\n 'ene',\n 'feb',\n 'mar',\n 'abr',\n 'may',\n 'jun',\n 'jul',\n 'ago',\n 'sep',\n 'oct',\n 'nov',\n 'dic',\n];\n\n/** Weekday names Monday-first (the calendar's week start, decided). */\nconst WEEKDAYS_SHORT = ['lun', 'mar', 'mié', 'jue', 'vie', 'sáb', 'dom'];\nconst WEEKDAYS = ['lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado', 'domingo'];\n\n/** Weekday headings of the Monday-first grid, in display order. */\nexport const WEEKDAY_HEADINGS: readonly string[] = WEEKDAYS_SHORT;\n\nconst DAY_PATTERN = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\n\n/**\n * `YYYY-MM-DD` → UTC milliseconds at midnight of that civil day.\n *\n * Strict: the string must be exactly three zero-padded parts and name a real\n * calendar day (`2026-02-30` is rejected, not rolled into March). Empty,\n * malformed or non-string input → `null` (the caller decides whether that\n * means \"undated\" or \"invalid\").\n */\nexport function parseDay(value: unknown): number | null {\n if (typeof value !== 'string') return null;\n const match = DAY_PATTERN.exec(value.trim());\n if (match === null) return null;\n const year = Number(match[1]);\n const month = Number(match[2]);\n const day = Number(match[3]);\n const ms = Date.UTC(year, month - 1, day);\n const back = new Date(ms);\n if (\n back.getUTCFullYear() !== year ||\n back.getUTCMonth() !== month - 1 ||\n back.getUTCDate() !== day\n ) {\n return null;\n }\n return ms;\n}\n\n/** UTC milliseconds at midnight → `YYYY-MM-DD` (inverse of `parseDay`). */\nexport function formatDay(ms: number): string {\n const date = new Date(ms);\n const month = String(date.getUTCMonth() + 1).padStart(2, '0');\n const day = String(date.getUTCDate()).padStart(2, '0');\n return `${date.getUTCFullYear()}-${month}-${day}`;\n}\n\n/**\n * Today as a civil day, in the USER's local calendar date — the local parts\n * are read off `new Date()` and formatted manually, so no timezone shift can\n * sneak in through a round-trip.\n */\nexport function todayCivil(): string {\n const now = new Date();\n const month = String(now.getMonth() + 1).padStart(2, '0');\n const day = String(now.getDate()).padStart(2, '0');\n return `${now.getFullYear()}-${month}-${day}`;\n}\n\n/**\n * Token inyectable para \"hoy\" (YYYY-MM-DD). Permite a los tests fijar la\n * fecha del sistema sin depender del reloj real (evita flakiness por mes).\n * Por defecto usa `todayCivil()` (fecha real del sistema).\n */\nexport const CALENDAR_TODAY = new InjectionToken<() => string>(\n 'gas.admin.calendarToday',\n { providedIn: 'root', factory: () => todayCivil },\n);\n\n/** Day-step on civil days (safe: every `ms` is a UTC midnight). */\nexport function addDays(ms: number, days: number): number {\n return ms + days * DAY_MS;\n}\n\n/** 0 = Monday … 6 = Sunday (the calendar's Monday-first convention). */\nexport function dayOfWeekMon0(ms: number): number {\n return (new Date(ms).getUTCDay() + 6) % 7;\n}\n\n/**\n * Move a civil day by whole months, clamping the day-of-month to the target\n * month's length (Jan 31 + 1 month → Feb 28).\n */\nexport function shiftMonths(ms: number, months: number): number {\n const date = new Date(ms);\n const target = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + months, 1));\n const lastDay = new Date(\n Date.UTC(target.getUTCFullYear(), target.getUTCMonth() + 1, 0),\n ).getUTCDate();\n return Date.UTC(\n target.getUTCFullYear(),\n target.getUTCMonth(),\n Math.min(date.getUTCDate(), lastDay),\n );\n}\n\n/** Monday of the 6-week (42-day) grid that shows the cursor's month. */\nfunction monthGridStartMs(cursorMs: number): number {\n const date = new Date(cursorMs);\n const firstOfMonth = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1);\n return addDays(firstOfMonth, -dayOfWeekMon0(firstOfMonth));\n}\n\n/**\n * The 42 civil days of the month grid (6 Monday-first weeks), so the grid\n * always has the same shape and the de-emphasized edge days are complete.\n */\nexport function monthGridDays(cursorDay: string): string[] {\n const cursor = parseDay(cursorDay);\n if (cursor === null) return [];\n const start = monthGridStartMs(cursor);\n return Array.from({ length: 42 }, (_, index) => formatDay(addDays(start, index)));\n}\n\n/** The 7 civil days of the Monday-first week containing the cursor. */\nexport function weekDays(cursorDay: string): string[] {\n const cursor = parseDay(cursorDay);\n if (cursor === null) return [];\n const start = addDays(cursor, -dayOfWeekMon0(cursor));\n return Array.from({ length: 7 }, (_, index) => formatDay(addDays(start, index)));\n}\n\n/** Every civil day of an inclusive range (empty when the range is invalid). */\nexport function daysInRange(range: CivilRange): string[] {\n const from = parseDay(range.desde);\n const to = parseDay(range.hasta);\n if (from === null || to === null || to < from) return [];\n const days: string[] = [];\n for (let ms = from; ms <= to; ms = addDays(ms, 1)) {\n days.push(formatDay(ms));\n }\n return days;\n}\n\n/**\n * Visible `{desde, hasta}` for a mode + cursor.\n *\n * - `month`: the 6-week grid range (superset of the month) — with the\n * backend-range optimization the fetch already covers the edge weeks.\n * - `week`: the Monday-first week of the cursor.\n * - `day`: the cursor itself.\n * - `agenda`: a fixed 30-day window from the cursor.\n */\nexport function visibleRangeFor(mode: CalendarMode, cursorDay: string): CivilRange {\n const cursor = parseDay(cursorDay);\n if (cursor === null) return { desde: '', hasta: '' };\n switch (mode) {\n case 'month': {\n const start = monthGridStartMs(cursor);\n return { desde: formatDay(start), hasta: formatDay(addDays(start, 41)) };\n }\n case 'week': {\n const start = addDays(cursor, -dayOfWeekMon0(cursor));\n return { desde: formatDay(start), hasta: formatDay(addDays(start, 6)) };\n }\n case 'day':\n return { desde: formatDay(cursor), hasta: formatDay(cursor) };\n case 'agenda':\n return { desde: formatDay(cursor), hasta: formatDay(addDays(cursor, 29)) };\n }\n}\n\n/** Move the cursor one step of the current mode (agenda steps 30 days). */\nexport function shiftCursor(mode: CalendarMode, cursorDay: string, delta: 1 | -1): string {\n const cursor = parseDay(cursorDay) ?? parseDay(todayCivil()) ?? Date.UTC(1970, 0, 1);\n switch (mode) {\n case 'month':\n return formatDay(shiftMonths(cursor, delta));\n case 'week':\n return formatDay(addDays(cursor, 7 * delta));\n case 'day':\n return formatDay(addDays(cursor, delta));\n case 'agenda':\n return formatDay(addDays(cursor, 30 * delta));\n }\n}\n\n/** True when both civil days fall in the same year+month. */\nexport function sameMonth(a: string, b: string): boolean {\n const msA = parseDay(a);\n const msB = parseDay(b);\n if (msA === null || msB === null) return false;\n const dateA = new Date(msA);\n const dateB = new Date(msB);\n return (\n dateA.getUTCFullYear() === dateB.getUTCFullYear() && dateA.getUTCMonth() === dateB.getUTCMonth()\n );\n}\n\n/** Depth-first lookup of a field by key through `group`/`list` nesting. */\nexport function findFieldByKey(\n fields: readonly FieldSchema[],\n key: string,\n): FieldSchema | undefined {\n for (const field of fields) {\n if (field.key === key) return field;\n if (field.type === 'group') {\n const hit = findFieldByKey(field.fields, key);\n if (hit !== undefined) return hit;\n } else if (field.type === 'list') {\n const hit = findFieldByKey(field.itemFields, key);\n if (hit !== undefined) return hit;\n }\n }\n return undefined;\n}\n\n/**\n * Pure row → event mapping (R9.8.3): the single place that decides what is a\n * paintable event, what is undated and what is an impossible interval.\n *\n * - Empty/invalid `startField` → `undated` (surfaced, never hidden).\n * - `endField` of type `time` → same civil day as the start (time-of-day\n * within the start day; it never spans days).\n * - Empty `endField` (date) → single-day event on the start.\n * - `end < start` → `invalid` with a visible reason; not painted at all.\n */\nexport function mapCalendarRows(\n rows: readonly SourceRow[],\n config: CalendarViewConfig,\n options: { keyField: string; endFieldType?: string },\n): CalendarMapping {\n const events: CalEvent[] = [];\n const undated: SourceRow[] = [];\n const invalid: CalendarProblem[] = [];\n\n for (const row of rows) {\n const startMs = parseDay(row[config.startField]);\n if (startMs === null) {\n undated.push(row);\n continue;\n }\n let endMs = startMs;\n if (config.endField !== undefined && options.endFieldType !== 'time') {\n const rawEnd = row[config.endField];\n const empty = rawEnd === null || rawEnd === undefined || rawEnd === '';\n if (!empty) {\n const parsedEnd = parseDay(rawEnd);\n if (parsedEnd === null) {\n invalid.push({ row, reason: REASON_INVALID_END });\n continue;\n }\n if (parsedEnd < startMs) {\n invalid.push({ row, reason: REASON_END_BEFORE_START });\n continue;\n }\n endMs = parsedEnd;\n }\n }\n const key = String(row[options.keyField] ?? '');\n const title = String(row[config.titleField] ?? '').trim() || key;\n const rawColor = config.colorField !== undefined ? row[config.colorField] : undefined;\n const color = typeof rawColor === 'string' && rawColor !== '' ? rawColor : undefined;\n events.push({ key, title, startDay: formatDay(startMs), endDay: formatDay(endMs), color, row });\n }\n\n return { events, undated, invalid };\n}\n\n/**\n * Bucket events per civil day inside the visible range.\n *\n * A multi-day event appears under EVERY day it touches; the painted span is\n * clamped to the range (the event keeps its true `startDay`/`endDay` — only\n * the buckets are limited), which also keeps long intervals cheap.\n */\nexport function eventsByDay(\n events: readonly CalEvent[],\n range: CivilRange,\n): Map<string, CalEvent[]> {\n const byDay = new Map<string, CalEvent[]>();\n const from = parseDay(range.desde);\n const to = parseDay(range.hasta);\n if (from === null || to === null || to < from) return byDay;\n for (const event of events) {\n const start = parseDay(event.startDay);\n const end = parseDay(event.endDay);\n if (start === null || end === null) continue;\n const paintFrom = Math.max(start, from);\n const paintTo = Math.min(end, to);\n for (let ms = paintFrom; ms <= paintTo; ms = addDays(ms, 1)) {\n const day = formatDay(ms);\n const bucket = byDay.get(day);\n if (bucket === undefined) byDay.set(day, [event]);\n else bucket.push(event);\n }\n }\n return byDay;\n}\n\n/** \"12 de octubre de 2026\" — civil parts, UTC-based (no TZ shift). */\nexport function dayLabel(day: string): string {\n const ms = parseDay(day);\n if (ms === null) return day;\n const date = new Date(ms);\n return `${date.getUTCDate()} de ${MONTHS[date.getUTCMonth()]} de ${date.getUTCFullYear()}`;\n}\n\n/** Short weekday of a civil day, Monday-first (\"lunes\" → \"lun\"). */\nexport function weekdayShort(day: string): string {\n const ms = parseDay(day);\n if (ms === null) return '';\n return WEEKDAYS_SHORT[dayOfWeekMon0(ms)];\n}\n\n/** Full weekday of a civil day (\"lunes\"). */\nexport function weekdayFull(day: string): string {\n const ms = parseDay(day);\n if (ms === null) return '';\n return WEEKDAYS[dayOfWeekMon0(ms)];\n}\n\n/** \"lunes, 12 de octubre de 2026\" — agenda/day heading. */\nexport function agendaDayLabel(day: string): string {\n const weekday = weekdayFull(day);\n return weekday !== '' ? `${weekday}, ${dayLabel(day)}` : dayLabel(day);\n}\n\n/**\n * Header label for the visible range, built from civil parts with a fixed\n * Spanish vocabulary (deterministic regardless of the runtime locale).\n */\nexport function rangeLabelFor(mode: CalendarMode, cursorDay: string): string {\n const cursor = parseDay(cursorDay);\n if (cursor === null) return '';\n const date = new Date(cursor);\n if (mode === 'month') {\n return `${MONTHS[date.getUTCMonth()]} de ${date.getUTCFullYear()}`;\n }\n const range = visibleRangeFor(mode, cursorDay);\n const desde = parseDay(range.desde);\n const hasta = parseDay(range.hasta);\n if (desde === null || hasta === null) return '';\n const startDate = new Date(desde);\n const endDate = new Date(hasta);\n const sameYear = startDate.getUTCFullYear() === endDate.getUTCFullYear();\n const left = `${startDate.getUTCDate()} ${MONTHS_SHORT[startDate.getUTCMonth()]}${\n sameYear ? '' : ` ${startDate.getUTCFullYear()}`\n }`;\n return `${left} – ${endDate.getUTCDate()} ${MONTHS_SHORT[endDate.getUTCMonth()]} ${endDate.getUTCFullYear()}`;\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n computed,\n effect,\n inject,\n signal,\n} from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { ActivatedRoute } from '@angular/router';\nimport { LucideChevronLeft, LucideChevronRight } from '@lucide/angular';\n\nimport type {\n CalendarViewConfig,\n ResourceSchema,\n UiViewCalendar,\n SelectionBinding,\n} from '../../core/models/schema.model';\nimport { ErrorMapperService } from '../../core/services/error-mapper.service';\nimport { DataSourceService, type SourceRow } from '../../core/services/data-source.service';\nimport type { SelectionEmitter } from '../composer/view-context';\nimport { getSchema } from '../../core/registry/schema-catalog';\nimport { Button } from '../../shared/components/button/button';\nimport { EmptyState } from '../../shared/components/empty-state/empty-state';\nimport { Skeleton } from '../../shared/components/skeleton/skeleton';\nimport {\n agendaDayLabel,\n type CalendarMapping,\n type CalendarMode,\n type CalEvent,\n type CivilRange,\n daysInRange,\n eventsByDay,\n findFieldByKey,\n mapCalendarRows,\n monthGridDays,\n rangeLabelFor,\n REASON_END_BEFORE_START,\n REASON_UNDATED,\n sameMonth,\n shiftCursor,\n CALENDAR_TODAY,\n visibleRangeFor,\n weekDays,\n WEEKDAY_HEADINGS,\n weekdayShort,\n} from './calendar-model';\n\n/**\n * Narrowing helper: `UiViewBase` keeps `type: string` open, so comparing\n * `view.type === 'calendar'` alone does not remove the base member from the\n * union. Requiring `config` in the value is what actually proves the view is\n * a `UiViewCalendar` at runtime.\n */\nfunction calendarConfigOf(view: unknown): UiViewCalendar['config'] | undefined {\n if (!view || typeof view !== 'object') return undefined;\n const candidate = view as { type?: unknown; config?: unknown };\n return candidate.type === 'calendar' && candidate.config !== undefined\n ? (candidate.config as UiViewCalendar['config'])\n : undefined;\n}\n\ntype Status = 'loading' | 'ready' | 'error';\n\n/** Mode switcher, in display order (Spanish UI copy). */\nconst MODE_OPTIONS: readonly { mode: CalendarMode; label: string }[] = [\n { mode: 'month', label: 'Mes' },\n { mode: 'week', label: 'Semana' },\n { mode: 'day', label: 'Día' },\n { mode: 'agenda', label: 'Agenda' },\n];\n\n/** Stable empty bucket — avoids allocating a new array per day per cycle. */\nconst NO_EVENTS: readonly CalEvent[] = [];\n\n/**\n * Generic calendar view (R9.8.3): month / week / day / agenda over the rows\n * of the declared `CalendarViewConfig.source` (or the resource's own\n * listing), positioned client-side onto civil days.\n *\n * Data flow: ONE fetcher (`DataSourceService.rows`) per visible range. When\n * the schema declares `source.view` the visible `desde`/`hasta` travel as\n * `ctx` so the backend view can filter the range; without a view nothing\n * filters, the full listing is cached (30 s TTL) and reused while the user\n * navigates — both paths share this component, only the `ctx` differs.\n *\n * Read-only by design: no drag, no click-to-edit. Color never carries\n * meaning alone — every event pairs its color dot with the title text — and\n * the agenda mode is the no-grid fallback for small screens.\n */\n@Component({\n selector: 'app-calendar-view',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [Button, EmptyState, LucideChevronLeft, LucideChevronRight, Skeleton],\n template: `\n @if (config(); as cfg) {\n <section class=\"space-y-4\" aria-label=\"Calendario\" [attr.aria-busy]=\"status() === 'loading'\">\n <header class=\"flex flex-wrap items-center justify-between gap-3\">\n <h2 class=\"font-display text-xl font-semibold\">{{ rangeLabel() }}</h2>\n <div class=\"flex items-center gap-1\">\n <button\n app-button\n variant=\"outline\"\n size=\"icon\"\n type=\"button\"\n aria-label=\"Período anterior\"\n title=\"Período anterior\"\n (click)=\"previous()\"\n >\n <svg lucideChevronLeft size=\"16\" aria-hidden=\"true\" />\n </button>\n <button app-button variant=\"outline\" size=\"sm\" type=\"button\" (click)=\"today()\">\n Hoy\n </button>\n <button\n app-button\n variant=\"outline\"\n size=\"icon\"\n type=\"button\"\n aria-label=\"Período siguiente\"\n title=\"Período siguiente\"\n (click)=\"next()\"\n >\n <svg lucideChevronRight size=\"16\" aria-hidden=\"true\" />\n </button>\n </div>\n <div class=\"flex items-center gap-1\" role=\"group\" aria-label=\"Vista del calendario\">\n @for (option of modeOptions; track option.mode) {\n <button\n app-button\n variant=\"ghost\"\n size=\"sm\"\n type=\"button\"\n [attr.aria-pressed]=\"mode() === option.mode\"\n (click)=\"setMode(option.mode)\"\n >\n {{ option.label }}\n </button>\n }\n </div>\n </header>\n\n @switch (status()) {\n @case ('loading') {\n <app-skeleton [lines]=\"8\" [height]=\"32\" />\n }\n @case ('error') {\n <app-empty-state title=\"No se pudo cargar el calendario\" [message]=\"errorMessage()\">\n <button app-button variant=\"outline\" type=\"button\" (click)=\"retry()\">\n Reintentar\n </button>\n </app-empty-state>\n }\n @case ('ready') {\n @if (mapping().events.length === 0) {\n <app-empty-state\n title=\"Sin eventos\"\n message=\"No hay eventos con fecha válida en este período.\"\n />\n } @else {\n @switch (mode()) {\n @case ('month') {\n <div class=\"overflow-x-auto rounded-xl border border-neutral/20 bg-surface\">\n <div class=\"grid min-w-[640px] grid-cols-7\">\n @for (heading of weekdayHeadings; track heading) {\n <div\n class=\"border-b border-neutral/15 px-2 py-1.5 text-xs font-medium uppercase text-neutral\"\n >\n {{ heading }}\n </div>\n }\n @for (day of monthGrid(); track day) {\n <div\n class=\"min-h-24 border-b border-r border-neutral/10 p-1.5\"\n [attr.data-day]=\"day\"\n [class.opacity-40]=\"!isInCursorMonth(day)\"\n >\n <span class=\"text-xs font-medium\">{{ dayNumber(day) }}</span>\n <ul class=\"mt-1 space-y-0.5\">\n @for (event of eventsFor(day); track event.key) {\n <li\n class=\"flex items-center gap-1 text-xs\"\n [class.cursor-pointer]=\"seleccionable()\"\n [class.ring-2]=\"esSeleccionada(event)\"\n [attr.data-event-key]=\"event.key\"\n [attr.data-selectable]=\"seleccionable() ? 'true' : null\"\n (click)=\"seleccionar(event)\"\n >\n <span\n class=\"size-2 shrink-0 rounded-full bg-neutral/30\"\n [style.background-color]=\"event.color ?? null\"\n aria-hidden=\"true\"\n ></span>\n <span class=\"truncate\">{{ event.title }}</span>\n </li>\n }\n </ul>\n </div>\n }\n </div>\n </div>\n }\n @case ('week') {\n <div class=\"overflow-x-auto rounded-xl border border-neutral/20 bg-surface\">\n <div class=\"grid min-w-[560px] grid-cols-7\">\n @for (day of weekColumnDays(); track day) {\n <div class=\"min-h-32 border-r border-neutral/10 p-2\" [attr.data-day]=\"day\">\n <span class=\"text-xs font-medium uppercase text-neutral\">\n {{ weekdayShort(day) }} {{ dayNumber(day) }}\n </span>\n <ul class=\"mt-2 space-y-1\">\n @for (event of eventsFor(day); track event.key) {\n <li\n class=\"flex items-center gap-1.5 text-sm\"\n [class.cursor-pointer]=\"seleccionable()\"\n [class.ring-2]=\"esSeleccionada(event)\"\n [attr.data-event-key]=\"event.key\"\n [attr.data-selectable]=\"seleccionable() ? 'true' : null\"\n (click)=\"seleccionar(event)\"\n >\n <span\n class=\"size-2 shrink-0 rounded-full bg-neutral/30\"\n [style.background-color]=\"event.color ?? null\"\n aria-hidden=\"true\"\n ></span>\n <span class=\"truncate\">{{ event.title }}</span>\n </li>\n }\n </ul>\n </div>\n }\n </div>\n </div>\n }\n @case ('day') {\n <div\n class=\"rounded-xl border border-neutral/20 bg-surface p-4\"\n [attr.data-day]=\"cursor()\"\n >\n <h3 class=\"font-display text-base font-semibold\">\n {{ dayHeading(cursor()) }}\n </h3>\n @if (eventsFor(cursor()).length === 0) {\n <p class=\"mt-2 text-sm text-neutral\">Sin eventos este día.</p>\n } @else {\n <ul class=\"mt-3 space-y-2\">\n @for (event of eventsFor(cursor()); track event.key) {\n <li\n class=\"flex items-center gap-1 text-xs\"\n [class.cursor-pointer]=\"seleccionable()\"\n [class.ring-2]=\"esSeleccionada(event)\"\n [attr.data-event-key]=\"event.key\"\n [attr.data-selectable]=\"seleccionable() ? 'true' : null\"\n (click)=\"seleccionar(event)\"\n >\n <span\n class=\"size-2.5 shrink-0 rounded-full bg-neutral/30\"\n [style.background-color]=\"event.color ?? null\"\n aria-hidden=\"true\"\n ></span>\n <span>{{ event.title }}</span>\n </li>\n }\n </ul>\n }\n </div>\n }\n @case ('agenda') {\n <ol class=\"space-y-4\">\n @for (day of agendaDays(); track day) {\n <li class=\"rounded-xl border border-neutral/20 bg-surface p-4\">\n <h3 class=\"text-sm font-semibold\">{{ dayHeading(day) }}</h3>\n <ul class=\"mt-2 space-y-1\">\n @for (event of eventsFor(day); track event.key) {\n <li\n class=\"flex items-center gap-2 text-sm\"\n [class.cursor-pointer]=\"seleccionable()\"\n [class.ring-2]=\"esSeleccionada(event)\"\n [attr.data-event-key]=\"event.key\"\n [attr.data-selectable]=\"seleccionable() ? 'true' : null\"\n (click)=\"seleccionar(event)\"\n >\n <span\n class=\"size-2 shrink-0 rounded-full bg-neutral/30\"\n [style.background-color]=\"event.color ?? null\"\n aria-hidden=\"true\"\n ></span>\n <span>{{ event.title }}</span>\n </li>\n }\n </ul>\n </li>\n }\n </ol>\n }\n }\n }\n\n <!-- Problem records are VISIBLE, never silently dropped: a row\n without a valid start or with an impossible interval cannot\n be painted, but the operator must see why. -->\n @if (hasProblemRecords()) {\n <section\n class=\"rounded-xl border border-neutral/30 p-4\"\n aria-label=\"Registros sin fecha válida\"\n >\n <h3 class=\"font-display text-sm font-semibold\">Registros sin fecha válida</h3>\n <p class=\"mt-1 text-help text-neutral\">\n Estos registros no se pueden pintar en el calendario:\n </p>\n <ul class=\"mt-2 space-y-1 text-sm\">\n @for (row of mapping().undated; track $index) {\n <li>{{ recordLabel(row) }} — {{ reasonUndated }}</li>\n }\n @for (item of mapping().invalid; track $index) {\n <li>{{ recordLabel(item.row) }} — {{ item.reason }}</li>\n }\n </ul>\n </section>\n }\n }\n }\n </section>\n } @else {\n <app-empty-state\n title=\"Configuración inválida\"\n message=\"Esta vista no declara una configuración de calendario válida.\"\n />\n }\n `,\n})\nexport class CalendarView {\n private readonly route = inject(ActivatedRoute);\n // The shell mounts views via `loadComponent` (no input bindings), so the\n // schema is resolved from route data exactly like FormView does.\n private readonly routeData = toSignal(this.route.data, { requireSync: true });\n private readonly dataSource = inject(DataSourceService);\n private readonly errorMapper = inject(ErrorMapperService);\n\n /**\n * Resource schema for this view. The shell mounts views via\n * `loadComponent` (no input bindings), so the schema is resolved from\n * route data: either an explicit `schema` object, or a `resourceId` looked\n * up in the registry (same route shape FormView uses).\n */\n readonly schema = computed<ResourceSchema | undefined>(() => {\n const data = this.routeData();\n const explicit = data['schema'];\n if (explicit !== undefined) return explicit as ResourceSchema;\n const resourceId = data['resourceId'];\n return typeof resourceId === 'string' ? getSchema(resourceId) : undefined;\n });\n\n /**\n * Config resolved from `schema.uiViews[<route uiView>]`; `undefined` when\n * the route does not name a calendar view with a usable config — the\n * template then renders the invalid-config empty state instead of a grid.\n */\n readonly config = computed<CalendarViewConfig | undefined>(() => {\n const schema = this.schema();\n const viewName = this.routeData()['uiView'];\n if (schema === undefined || typeof viewName !== 'string') return undefined;\n const cfg = calendarConfigOf(schema.uiViews?.[viewName]);\n if (cfg === undefined) return undefined;\n const usable =\n typeof cfg.startField === 'string' &&\n cfg.startField !== '' &&\n typeof cfg.titleField === 'string' &&\n cfg.titleField !== '';\n return usable ? cfg : undefined;\n });\n\n /** Explicit user choice; `undefined` ⇒ the config's `defaultView`. */\n private readonly explicitMode = signal<CalendarMode | undefined>(undefined);\n\n readonly mode = computed<CalendarMode>(\n () => this.explicitMode() ?? this.config()?.defaultView ?? 'month',\n );\n\n /** Anchor civil day of the visible range; init = today (civil). */\n readonly cursor = signal<string>(inject(CALENDAR_TODAY)());\n\n /** \"Hoy\" inyectable (default: fecha real del sistema). */\n private readonly todayFn = inject(CALENDAR_TODAY);\n\n /** Visible `{desde, hasta}` derived from cursor + mode. */\n readonly visibleRange = computed<CivilRange | null>(() =>\n this.config() === undefined ? null : visibleRangeFor(this.mode(), this.cursor()),\n );\n\n readonly rangeLabel = computed(() =>\n this.config() === undefined ? '' : rangeLabelFor(this.mode(), this.cursor()),\n );\n\n readonly status = signal<Status>('loading');\n readonly rows = signal<SourceRow[]>([]);\n /** Clave seleccionada localmente cuando el binding declara `mode:'inline'`. */\n readonly seleccionada = signal<string | null>(null);\n readonly errorMessage = signal('');\n\n readonly modeOptions = MODE_OPTIONS;\n readonly weekdayHeadings = WEEKDAY_HEADINGS;\n readonly reasonUndated = REASON_UNDATED;\n\n /** Guards against a slow response overwriting a newer range's rows. */\n private loadEpoch = 0;\n\n constructor() {\n effect(() => {\n const cfg = this.config();\n const range = this.visibleRange();\n if (cfg === undefined || range === null) return;\n void this.load(cfg, range);\n });\n }\n\n /** Rows mapped to events + visible problem records (pure model). */\n readonly mapping = computed<CalendarMapping>(() => {\n const cfg = this.config();\n const schema = this.schema();\n if (cfg === undefined || schema === undefined) return { events: [], undated: [], invalid: [] };\n const endFieldType =\n cfg.endField !== undefined ? findFieldByKey(schema.fields, cfg.endField)?.type : undefined;\n return mapCalendarRows(this.rows(), cfg, {\n keyField: schema.keyField,\n endFieldType,\n });\n });\n\n /** Events bucketed per civil day, clamped to the visible range. */\n readonly eventsByDay = computed(() => {\n const range = this.visibleRange();\n if (range === null) return new Map<string, CalEvent[]>();\n return eventsByDay(this.mapping().events, range);\n });\n\n readonly monthGrid = computed(() => monthGridDays(this.cursor()));\n readonly weekColumnDays = computed(() => weekDays(this.cursor()));\n\n /** Agenda headings: the days of the range that actually have events. */\n readonly agendaDays = computed(() => {\n const range = this.visibleRange();\n if (range === null) return [] as string[];\n return daysInRange(range).filter((day) => this.eventsByDay().has(day));\n });\n\n readonly hasProblemRecords = computed(\n () => this.mapping().undated.length > 0 || this.mapping().invalid.length > 0,\n );\n\n previous(): void {\n this.cursor.set(shiftCursor(this.mode(), this.cursor(), -1));\n }\n\n next(): void {\n this.cursor.set(shiftCursor(this.mode(), this.cursor(), 1));\n }\n\n /** Reset the cursor to today (civil). */\n today(): void {\n this.cursor.set(this.todayFn());\n }\n\n setMode(mode: CalendarMode): void {\n this.explicitMode.set(mode);\n }\n\n retry(): void {\n const cfg = this.config();\n const range = this.visibleRange();\n if (cfg !== undefined && range !== null) void this.load(cfg, range);\n }\n\n /** Binding `select` declarado por la vista (R9.9.4 · decision table). */\n private binding(): SelectionBinding | undefined {\n return this.routeData()['select'] as SelectionBinding | undefined;\n }\n\n /**\n * Clic de evento. Sin declaración ⇒ inerte (el calendario no navegaba);\n * con declaración ⇒ sólo el contrato declarado corre: `route` emite al\n * dispatcher, `inline` selecciona en local, `none` es no-op explícito.\n */\n seleccionar(event: CalEvent): void {\n const binding = this.binding();\n if (binding === undefined) return;\n if (binding.mode === 'inline') {\n this.seleccionada.set(event.key);\n return;\n }\n if (binding.mode === 'none') return;\n const schema = this.schema();\n const emit = this.routeData()['emit'] as SelectionEmitter | undefined;\n emit?.({\n resource: schema?.id ?? String(this.routeData()['resourceId'] ?? ''),\n key: event.key,\n action: binding.on,\n });\n }\n\n /** El DOM declara clicable sólo si hay contrato que responder. */\n seleccionable(): boolean {\n const binding = this.binding();\n return binding !== undefined && binding.mode !== 'none';\n }\n\n esSeleccionada(event: CalEvent): boolean {\n return this.seleccionable() && this.seleccionada() === event.key;\n }\n\n eventsFor(day: string): readonly CalEvent[] {\n return this.eventsByDay().get(day) ?? NO_EVENTS;\n }\n\n dayNumber(day: string): number {\n return Number(day.slice(8, 10));\n }\n\n isInCursorMonth(day: string): boolean {\n return sameMonth(day, this.cursor());\n }\n\n /** Weekday + number (\"lun 12\") for week columns. */\n weekdayShort(day: string): string {\n return weekdayShort(day);\n }\n\n /** Full heading (\"lunes, 12 de octubre de 2026\") for day/agenda. */\n dayHeading(day: string): string {\n return agendaDayLabel(day);\n }\n\n recordLabel(row: SourceRow): string {\n const schema = this.schema();\n if (schema === undefined) return '';\n const title = String(row[schema.titleField] ?? '').trim();\n return title !== '' ? title : String(row[schema.keyField] ?? '');\n }\n\n private async load(cfg: CalendarViewConfig, range: CivilRange): Promise<void> {\n const schema = this.schema();\n if (schema === undefined) return;\n const epoch = ++this.loadEpoch;\n this.status.set('loading');\n // Optimization branch (decided): range ctx ONLY when the schema declares\n // a backend view — that's the signal that a `where` may filter on\n // `param: 'desde'`/`'hasta'`. Without a view the params would be ignored\n // (and the full listing is cached while the user navigates).\n const ctx =\n cfg.source?.view !== undefined && cfg.source.view !== ''\n ? { desde: range.desde, hasta: range.hasta }\n : undefined;\n const source = cfg.source ?? { resource: schema.id };\n try {\n const rows = await this.dataSource.rows(source, ctx);\n if (epoch !== this.loadEpoch) return;\n this.rows.set(rows);\n this.errorMessage.set('');\n this.status.set('ready');\n } catch (error) {\n if (epoch !== this.loadEpoch) return;\n this.errorMessage.set(\n this.errorMapper.map(error).general ?? 'No se pudo cargar el calendario.',\n );\n this.status.set('error');\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as getSchema } from "./tinya-code-gas-admin-ui-schema-catalog-
|
|
1
|
+
import { i as getSchema } from "./tinya-code-gas-admin-ui-schema-catalog-CsB26qUM.mjs";
|
|
2
2
|
import * as i0 from "@angular/core";
|
|
3
3
|
import { Injectable, inject } from "@angular/core";
|
|
4
4
|
import { Router } from "@angular/router";
|
|
@@ -98,4 +98,4 @@ function contains(actual, expected) {
|
|
|
98
98
|
}
|
|
99
99
|
export { ViewNavigationService as n, evaluateCondition as t };
|
|
100
100
|
|
|
101
|
-
//# sourceMappingURL=tinya-code-gas-admin-ui-condition-evaluator-
|
|
101
|
+
//# sourceMappingURL=tinya-code-gas-admin-ui-condition-evaluator-CHzJuOx2.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tinya-code-gas-admin-ui-condition-evaluator-
|
|
1
|
+
{"version":3,"file":"tinya-code-gas-admin-ui-condition-evaluator-CHzJuOx2.mjs","names":[],"sources":["../../../projects/gas-admin-ui/src/lib/core/view-navigation/view-navigation.service.ts","../../../projects/gas-admin-ui/src/lib/shared/utils/condition-evaluator.ts"],"sourcesContent":["import { Injectable, inject } from '@angular/core';\nimport { Router, type ActivatedRouteSnapshot } from '@angular/router';\n\nimport type { NavigationTarget, SelectionBinding } from '../models/schema.model';\nimport { getSchema } from '../registry/schema-catalog';\nimport type { SelectionEvent } from '../../views/composer/view-context';\n\n/**\n * R9.9 · D4 — Servicio de navegación declarativa.\n *\n * Un leaf (hoja) sólo NAVEGA cuando hay `select` declarado; el motor vive\n * aquí, no en cada hoja. El flujo completo es:\n *\n * `ctx.emit/leaf → dispatch → buildSegments(pattern, {key}) →\n * router.navigate(matriz de comandos) → roleGuard`\n *\n * Invariantes:\n *\n * - **key-only**: la ÚNICA identidad que viaja es `event.key`. Los comandos\n * son una matriz de segmentos (nunca concat de strings) ⇒ el Router\n * codifica la clave (una clave `A/B` queda `A%2FB` en la URL) y ningún\n * dato del registro llega a la URL.\n * - **Fail closed**: sin binding, con acción distinta a la declarada o con\n * `mode` distinto de `route`, NO hay navegación — simplemente no ocurre.\n * - **Fail loud**: un target que el registry no declara lanza ruido antes de\n * navegar (el validador pre-render ya lo garantiza; esto es defensa en\n * profundidad).\n * - **Guards reales**: `router.navigate` ⇒ `roleGuard` corre igual que en\n * cualquier navegación declarada. Si el guard bloquea, no se registra\n * origen (no hay «atrás» que una sesión sin permiso deba volver a ver).\n */\n\n/**\n * Patrón de segmentos de la ruta destino (`['orders', ':key']`). Usa la\n * MISMA fuente que el validador (`getSchema`) para que lo validado en build\n * sea exactamente lo navegado en runtime. Inexistente ⇒ `undefined` (la\n * defensa en profundidad: el validador pre-render garantiza que esto no\n * ocurre en schemas válidos).\n */\nexport function targetRoutePattern(target: NavigationTarget): string[] | undefined {\n const destino = getSchema(target.resource);\n const vista = destino?.uiViews?.[target.view];\n if (!destino || !vista) return undefined;\n const relativa = vista.path ?? '';\n return [destino.id, ...(relativa === '' ? [] : relativa.split('/'))];\n}\n\n/**\n * Segmentos de la ruta destino con la clave sustituida. La clave es SIEMPRE\n * un segmento completo (no se parte en `/`); un param distinto de `:key` no\n * tiene fuente declarada ⇒ falla ruido, no navega a medias.\n */\nexport function buildSegments(pattern: readonly string[], key: string): string[] {\n const commands: string[] = [];\n for (const segmento of pattern) {\n if (segmento.startsWith(':')) {\n const nombre = segmento.slice(1);\n if (nombre !== 'key') {\n throw new Error(`Destino ':${nombre}' sin fuente declarada en SelectionEvent`);\n }\n commands.push(key);\n } else {\n commands.push(segmento);\n }\n }\n return commands;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class ViewNavigationService {\n private readonly router = inject(Router);\n\n /** URL desde la que se navegó la última selección válida (origen del «atrás»). */\n private origen: string | undefined;\n\n /**\n * Transición declarada de un leaf. Inerte si no hay binding, si la acción\n * no es la declarada o si el modo no es `route`; falla ruido si el target\n * no existe en el registry.\n *\n * @returns `true` si el Router completó la navegación (guards aceptados).\n */\n async dispatch(binding: SelectionBinding | undefined, event: SelectionEvent): Promise<boolean> {\n if (binding === undefined) return false; // sin declaración ⇒ inerte\n if (binding.on !== event.action) return false; // fuera del scope declarado\n if (binding.mode !== 'route') return false; // comportamiento local del leaf\n\n const pattern = targetRoutePattern(binding.target);\n if (pattern === undefined) {\n throw new Error(\n `Destino '${binding.target.resource}/${binding.target.view}' no existe en el registry`,\n );\n }\n\n const urlOrigen = this.router.url;\n const ok = await this.router.navigate(['/', ...buildSegments(pattern, event.key)]);\n if (ok) this.origen = urlOrigen;\n return ok;\n }\n\n /**\n * Forma de volver (D4). Orden: `returnPath` declarado por el leaf ⇒\n * origen registrado por `dispatch` ⇒ raíz del recurso declarado en la ruta\n * actual (entrada directa a la URL: no hay origen en memoria). Fallback\n * final: la raíz de la aplicación.\n */\n async back(returnPath?: string): Promise<boolean> {\n if (returnPath !== undefined) return this.router.navigate([returnPath]);\n\n if (this.origen !== undefined) {\n const destino = this.origen;\n this.origen = undefined;\n return this.router.navigateByUrl(destino);\n }\n\n const recurso = this.recursoActual();\n return recurso === undefined\n ? this.router.navigate(['/'])\n : this.router.navigate(['/', recurso]);\n }\n\n /** Primer `resourceId` declarado en el árbol de rutas activo. */\n private recursoActual(): string | undefined {\n let nodo: ActivatedRouteSnapshot | null = this.router.routerState.snapshot.root;\n while (nodo !== null) {\n const id = nodo.data?.['resourceId'];\n if (id !== undefined) return String(id);\n nodo = nodo.firstChild;\n }\n return undefined;\n }\n}\n","import type { FieldCondition } from '../../core/models/schema.model';\n\n/**\n * Evaluador de condiciones `visibleWhen` / `readonlyWhen` (base.md §4):\n * comparaciones simples (igual, distinto, está vacío, contiene) sobre otros\n * campos del mismo nivel o del registro raíz.\n *\n * Nota de arquitectura: la importación de `FieldCondition` es SOLO de tipo\n * (se elimina en compilación) — no hay dependencia de runtime con `core`.\n */\n\n/** Resuelve una condición. Se resuelve contra el nivel propio; si la clave\n * no existe ahí, contra la raíz del registro. */\nexport function evaluateCondition(\n condition: FieldCondition,\n siblingValues: Record<string, unknown>,\n rootValues: Record<string, unknown> = {},\n): boolean {\n const source = condition.field in siblingValues ? siblingValues : rootValues;\n const actual = source[condition.field];\n const expected = condition.value;\n\n switch (condition.operator) {\n case 'eq':\n return actual === expected;\n case 'ne':\n return actual !== expected;\n case 'empty':\n return isEmpty(actual);\n case 'contains':\n return contains(actual, expected);\n }\n}\n\nfunction isEmpty(value: unknown): boolean {\n if (value === undefined || value === null || value === '') {\n return true;\n }\n if (Array.isArray(value)) {\n return value.length === 0;\n }\n return false;\n}\n\nfunction contains(actual: unknown, expected: unknown): boolean {\n if (typeof actual === 'string') {\n return actual.includes(String(expected));\n }\n if (Array.isArray(actual)) {\n return actual.includes(expected);\n }\n return false;\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as getSchema, l as ApiService } from "./tinya-code-gas-admin-ui-schema-catalog-
|
|
1
|
+
import { i as getSchema, l as ApiService } from "./tinya-code-gas-admin-ui-schema-catalog-CsB26qUM.mjs";
|
|
2
2
|
import { t as interpolateTemplate } from "./tinya-code-gas-admin-ui-template-interpolator-ewv7CkNX.mjs";
|
|
3
3
|
import * as i0 from "@angular/core";
|
|
4
4
|
import { Service, inject } from "@angular/core";
|
|
@@ -85,4 +85,4 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
85
85
|
});
|
|
86
86
|
export { dataSourceKey as n, DataSourceService as t };
|
|
87
87
|
|
|
88
|
-
//# sourceMappingURL=tinya-code-gas-admin-ui-data-source.service-
|
|
88
|
+
//# sourceMappingURL=tinya-code-gas-admin-ui-data-source.service-Cw1rdrWZ.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tinya-code-gas-admin-ui-data-source.service-
|
|
1
|
+
{"version":3,"file":"tinya-code-gas-admin-ui-data-source.service-Cw1rdrWZ.mjs","names":[],"sources":["../../../projects/gas-admin-ui/src/lib/core/services/data-source.service.ts"],"sourcesContent":["import { inject, Service } from '@angular/core';\nimport { firstValueFrom } from 'rxjs';\n\nimport type { DataSource } from '../models/schema.model';\nimport { interpolateTemplate } from '../utils/template-interpolator';\nimport { getSchema } from '../registry/schema-catalog';\nimport { ApiService } from './api.service';\n\n/** Fila cruda de un listado: el motor no impone forma a las columnas. */\nexport type SourceRow = Record<string, unknown>;\n\n/**\n * Cuánto se reutiliza una respuesta pedida igual (F8 · R6.5).\n *\n * Un panel de administración lee mucho y escribe poco: medio minuto de\n * frescura cubre un recorrido por varios formularios sin repetir la misma\n * vuelta. Es una decisión de UX, no de contrato — `invalidate()` la salta\n * cuando el usuario acaba de crear lo que la fuente lista.\n */\nconst TTL_MS = 30_000;\n\n/** Techo de claves cacheadas: un Map sin límite es una fuga en una sesión larga. */\nconst MAX_CLAVES = 100;\n\n/**\n * `source.params` con cada `{campo}` resuelto contra `ctx`.\n *\n * - Un valor que se resuelve **vacío se OMITE** en vez de mandar `''`: el\n * back distingue «parámetro ausente» (fail-closed ⇒ sin filas) de un\n * `eq ''` que podría cruzarse con filas realmente vacías.\n * - Un parámetro sin marcador `{}` es literal y viaja tal cual.\n */\nexport function interpolateParams(\n params: Record<string, string> | undefined,\n ctx?: Record<string, unknown>,\n): Record<string, string> {\n const out: Record<string, string> = {};\n if (params === undefined) return out;\n for (const name of Object.keys(params)) {\n const raw = params[name];\n const value = interpolateTemplate(raw, ctx ?? {});\n if (value !== '') out[name] = value;\n }\n return out;\n}\n\n/**\n * Clave de caché de una fuente: `resource|view|params` (F8 · R6.5).\n *\n * Los parámetros van **resueltos** y ordenados, así que dos fuentes que\n * terminan pidiendo lo mismo comparten entrada, y un `{campo}` que cambia\n * es otra entrada. Pura: es también el valor que `resource({ params })` de\n * Angular compara para decidir si vuelve a cargar.\n */\nexport function dataSourceKey(source: DataSource, ctx?: Record<string, unknown>): string {\n const params = interpolateParams(source.params, ctx);\n const names = Object.keys(params).sort();\n const query = names\n .map((name) => `${encodeURIComponent(name)}=${encodeURIComponent(params[name])}`)\n .join('&');\n return `${source.resource}|${source.view ?? ''}|${query}`;\n}\n\n/**\n * Fetcher único de `source` (F8 · R6.5): relations, selects y widgets del\n * panel piden sus filas ACÁ y no cada uno desde su componente.\n *\n * - **Caché por `resource+view+params`** con TTL e invalidación explícita —\n * es la pieza que evita que seis widgets del panel disparen seis vueltas\n * iguales a la misma vista.\n * - **Deduplicación de vuelta en vuelo**: dos consumidores que pidan la misma\n * clave en el mismo ciclo comparten la promesa (no la respuesta, la vuelta).\n * - Un error **no queda cacheado**: se borra la entrada para que la recarga\n * del consumidor vuelva a intentarlo en vez de devolver siempre la misma\n * rechazada.\n */\n@Service()\nexport class DataSourceService {\n private readonly api = inject(ApiService);\n\n private readonly cache = new Map<string, { promise: Promise<SourceRow[]>; at: number }>();\n\n /**\n * Filas de la fuente declarada.\n *\n * `fresh` salta la caché (recarga tras una escritura); `ctx` sólo se usa\n * para resolver `{campo}` en `params` y no cambia la forma de la respuesta.\n */\n async rows(\n source: DataSource,\n ctx?: Record<string, unknown>,\n fresh = false,\n ): Promise<SourceRow[]> {\n const key = dataSourceKey(source, ctx);\n if (!fresh) {\n const hit = this.cache.get(key);\n if (hit !== undefined && Date.now() - hit.at < TTL_MS) return hit.promise;\n }\n const promise = this.fetch(source, ctx).catch((error: unknown) => {\n this.cache.delete(key);\n throw error;\n });\n this.cache.set(key, { promise, at: Date.now() });\n if (this.cache.size > MAX_CLAVES) {\n // El Map conserva el orden de inserción: sale la más vieja.\n const oldest = this.cache.keys().next();\n if (!oldest.done) this.cache.delete(oldest.value);\n }\n return promise;\n }\n\n /**\n * Vacía las filas ya pedidas. Sin argumento, todas; con un `resourceId`,\n * sólo las de ese recurso (lo que acaba de crear el drawer de relación).\n */\n invalidate(resourceId?: string): void {\n if (resourceId === undefined) {\n this.cache.clear();\n return;\n }\n const prefix = `${resourceId}|`;\n for (const key of [...this.cache.keys()]) {\n if (key.startsWith(prefix)) this.cache.delete(key);\n }\n }\n\n private async fetch(source: DataSource, ctx?: Record<string, unknown>): Promise<SourceRow[]> {\n const target = getSchema(source.resource);\n if (!target) {\n // Un id que ni siquiera existe en el registry es error de\n // configuración, no de red: el consumidor lo pinta como «no se\n // pudieron cargar» con este texto.\n throw new Error(`Recurso desconocido: ${source.resource}`);\n }\n const payload: Record<string, unknown> = {};\n if (source.view !== undefined && source.view !== '') payload['view'] = source.view;\n const params = interpolateParams(source.params, ctx);\n if (Object.keys(params).length > 0) payload['params'] = params;\n const rows = await firstValueFrom(this.api.list<SourceRow>(target.endpoint, payload));\n return Array.isArray(rows) ? rows : [];\n }\n}\n"],"mappings":";;;;;AAmBA,MAAM,SAAS;AAGf,MAAM,aAAa;AAUnB,SAAgB,kBACd,QACA,KAA6B;CAE7B,MAAM,MAA8B,CAAA;CACpC,IAAI,WAAW,QAAW,OAAO;CACjC,KAAK,MAAM,QAAQ,OAAO,KAAK,MAAM,GAAG;EACtC,MAAM,MAAM,OAAO;EACnB,MAAM,QAAQ,oBAAoB,KAAK,OAAO,CAAA,CAAE;EAChD,IAAI,UAAU,IAAI,IAAI,QAAQ;CAChC;CACA,OAAO;AACT;AAUA,SAAgB,cAAc,QAAoB,KAA6B;CAC7E,MAAM,SAAS,kBAAkB,OAAO,QAAQ,GAAG;CAEnD,MAAM,QADQ,OAAO,KAAK,MAAM,CAAC,CAAC,KACpB,CAAA,CACX,KAAK,SAAS,GAAG,mBAAmB,IAAI,EAAC,GAAI,mBAAmB,OAAO,KAAK,GAAG,CAAC,CAChF,KAAK,GAAG;CACX,OAAO,GAAG,OAAO,SAAQ,GAAI,OAAO,QAAQ,GAAE,GAAI;AACpD;AAgBA,IAAa,oBAAb,MAAa,kBAAiB;CACX,MAAM,OAAO,UAAU;CAEvB,wBAAQ,IAAI,IAAG;CAQhC,MAAM,KACJ,QACA,KACA,QAAQ,OAAK;EAEb,MAAM,MAAM,cAAc,QAAQ,GAAG;EACrC,IAAI,CAAC,OAAO;GACV,MAAM,MAAM,KAAK,MAAM,IAAI,GAAG;GAC9B,IAAI,QAAQ,UAAa,KAAK,IAAG,IAAK,IAAI,KAAK,QAAQ,OAAO,IAAI;EACpE;EACA,MAAM,UAAU,KAAK,MAAM,QAAQ,GAAG,CAAC,CAAC,OAAO,UAAkB;GAC/D,KAAK,MAAM,OAAO,GAAG;GACrB,MAAM;EACR,CAAC;EACD,KAAK,MAAM,IAAI,KAAK;GAAE;GAAS,IAAI,KAAK,IAAG;EAAE,CAAE;EAC/C,IAAI,KAAK,MAAM,OAAO,YAAY;GAEhC,MAAM,SAAS,KAAK,MAAM,KAAI,CAAE,CAAC,KAAI;GACrC,IAAI,CAAC,OAAO,MAAM,KAAK,MAAM,OAAO,OAAO,KAAK;EAClD;EACA,OAAO;CACT;CAMA,WAAW,YAAmB;EAC5B,IAAI,eAAe,QAAW;GAC5B,KAAK,MAAM,MAAK;GAChB;EACF;EACA,MAAM,SAAS,GAAG,WAAU;EAC5B,KAAK,MAAM,OAAO,CAAC,GAAG,KAAK,MAAM,KAAI,CAAE,GACrC,IAAI,IAAI,WAAW,MAAM,GAAG,KAAK,MAAM,OAAO,GAAG;CAErD;CAEQ,MAAM,MAAM,QAAoB,KAA6B;EACnE,MAAM,SAAS,UAAU,OAAO,QAAQ;EACxC,IAAI,CAAC,QAIH,MAAM,IAAI,MAAM,wBAAwB,OAAO,UAAU;EAE3D,MAAM,UAAmC,CAAA;EACzC,IAAI,OAAO,SAAS,UAAa,OAAO,SAAS,IAAI,QAAQ,UAAU,OAAO;EAC9E,MAAM,SAAS,kBAAkB,OAAO,QAAQ,GAAG;EACnD,IAAI,OAAO,KAAK,MAAM,CAAC,CAAC,SAAS,GAAG,QAAQ,YAAY;EACxD,MAAM,OAAO,MAAM,eAAe,KAAK,IAAI,KAAgB,OAAO,UAAU,OAAO,CAAC;EACpF,OAAO,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAA;CACtC;CA/DW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAiB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAjB,OAAA,QAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;CAAiB,CAAA;;AAAjB,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAiB,YAAA,CAAA,EAD7B,MAAA,QAAA,CAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { b as ApiError } from "./tinya-code-gas-admin-ui-schema-catalog-CsB26qUM.mjs";
|
|
2
2
|
import * as i0 from "@angular/core";
|
|
3
3
|
import { Service } from "@angular/core";
|
|
4
4
|
var ErrorMapperService = class ErrorMapperService {
|
|
@@ -42,4 +42,4 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
42
42
|
});
|
|
43
43
|
export { ErrorMapperService as t };
|
|
44
44
|
|
|
45
|
-
//# sourceMappingURL=tinya-code-gas-admin-ui-error-mapper.service-
|
|
45
|
+
//# sourceMappingURL=tinya-code-gas-admin-ui-error-mapper.service-BQ--gSnT.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tinya-code-gas-admin-ui-error-mapper.service-
|
|
1
|
+
{"version":3,"file":"tinya-code-gas-admin-ui-error-mapper.service-BQ--gSnT.mjs","names":[],"sources":["../../../projects/gas-admin-ui/src/lib/core/services/error-mapper.service.ts"],"sourcesContent":["import { Service } from '@angular/core';\n\nimport { ApiError } from '../models/api.model';\n\nexport interface MappedError {\n /** Errores por ruta de campo (ej. `faq[2].answer`); se pintan en el campo exacto. */\n fields: Record<string, string>;\n /** Mensaje general; presente cuando el error no tiene ruta de campo (base.md §10). */\n general?: string;\n}\n\n/**\n * Convierte un error recibido en el formato que consume la UI:\n * rutas de campo para pintar el error donde corresponde, o mensaje general.\n *\n * Un422 trae VARIOS issues con ruta (`error.errors[]`, auth.md §6); cada uno\n * se convierte en `fields[path] = mensaje`, que es exactamente la clave que\n * después resuelve el host como `serverErrors[prefix + key]`. Si al final no\n * hay ninguna ruta pintable (error sin `errors[]`, issue sin `path`), el\n * mensaje del sobre queda en `general` como hasta ahora (base.md §10).\n */\n@Service()\nexport class ErrorMapperService {\n map(error: unknown): MappedError {\n if (error instanceof ApiError) {\n const fields: Record<string, string> = {};\n for (const issue of error.issues) {\n if (issue.path) {\n fields[issue.path] = issue.message;\n }\n }\n // Sobre con `path` suelto (sin `errors[]`): misma historia, un campo.\n if (error.fieldPath && fields[error.fieldPath] === undefined) {\n fields[error.fieldPath] = error.message;\n }\n if (Object.keys(fields).length > 0) {\n return { fields };\n }\n }\n return { fields: {}, general: this.messageOf(error) };\n }\n\n messageOf(error: unknown): string {\n if (error instanceof Error) {\n return error.message;\n }\n return 'Error inesperado';\n }\n}\n"],"mappings":";;;AAsBA,IAAa,qBAAb,MAAa,mBAAkB;CAC7B,IAAI,OAAc;EAChB,IAAI,iBAAiB,UAAU;GAC7B,MAAM,SAAiC,CAAA;GACvC,KAAK,MAAM,SAAS,MAAM,QACxB,IAAI,MAAM,MACR,OAAO,MAAM,QAAQ,MAAM;GAI/B,IAAI,MAAM,aAAa,OAAO,MAAM,eAAe,QACjD,OAAO,MAAM,aAAa,MAAM;GAElC,IAAI,OAAO,KAAK,MAAM,CAAC,CAAC,SAAS,GAC/B,OAAO,EAAE,OAAM;EAEnB;EACA,OAAO;GAAE,QAAQ,CAAA;GAAI,SAAS,KAAK,UAAU,KAAK;EAAC;CACrD;CAEA,UAAU,OAAc;EACtB,IAAI,iBAAiB,OACnB,OAAO,MAAM;EAEf,OAAO;CACT;CAzBW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAkB,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAlB,OAAA,QAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;CAAkB,CAAA;;AAAlB,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAkB,YAAA,CAAA,EAD9B,MAAA,QAAA,CAAA"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { t as __exportAll } from "./tinya-code-gas-admin-ui-rolldown-runtime-DpJrqANO.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { b as ApiError } from "./tinya-code-gas-admin-ui-schema-catalog-CsB26qUM.mjs";
|
|
3
3
|
import { t as Button } from "./tinya-code-gas-admin-ui-button-BQjNW8KB.mjs";
|
|
4
4
|
import { t as NotificationService } from "./tinya-code-gas-admin-ui-notification.service-LjSULER6.mjs";
|
|
5
|
-
import { t as UploadService } from "./tinya-code-gas-admin-ui-upload.service-
|
|
5
|
+
import { t as UploadService } from "./tinya-code-gas-admin-ui-upload.service-BNvH04S3.mjs";
|
|
6
6
|
import * as i0 from "@angular/core";
|
|
7
7
|
import { Component, DestroyRef, computed, inject, input, model, output, signal, viewChild } from "@angular/core";
|
|
8
8
|
import { HttpEventType } from "@angular/common/http";
|
|
@@ -405,4 +405,4 @@ function acceptMatches(accept, file) {
|
|
|
405
405
|
}
|
|
406
406
|
export { file_exports as n, FieldFile as t };
|
|
407
407
|
|
|
408
|
-
//# sourceMappingURL=tinya-code-gas-admin-ui-file-
|
|
408
|
+
//# sourceMappingURL=tinya-code-gas-admin-ui-file-JstsF8C5.mjs.map
|