@maykonpaulo/maestro-admin 0.1.2 → 0.2.0-next.1

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/dist/index.d.ts CHANGED
@@ -86,12 +86,19 @@ interface SortInput {
86
86
  field: string;
87
87
  direction: 'asc' | 'desc';
88
88
  }
89
+ /** One filter clause, serialized to the server's `filter=field:operator:value` query param. */
90
+ interface FilterInput {
91
+ field: string;
92
+ operator: 'equals' | 'notEquals' | 'contains' | 'startsWith' | 'endsWith' | 'in' | 'notIn' | 'gt' | 'gte' | 'lt' | 'lte' | 'between' | 'isNull' | 'isNotNull' | 'isTrue' | 'isFalse';
93
+ value?: string;
94
+ }
89
95
  interface ListQuery {
90
96
  page?: number;
91
97
  pageSize?: number;
92
98
  search?: string;
93
99
  searchFields?: string[];
94
100
  sort?: SortInput[];
101
+ filters?: FilterInput[];
95
102
  }
96
103
 
97
104
  interface MaestroClientConfig {
@@ -128,6 +135,82 @@ declare class MaestroClient {
128
135
  remove(entityId: string, id: string): Promise<void>;
129
136
  }
130
137
 
138
+ type AdminLocale = 'en' | 'pt-BR';
139
+ /**
140
+ * Every user-facing string the admin chrome renders. Entity/field labels are NOT here — those come
141
+ * from the server metadata (optionally rewritten via `AdminLabels`); this dictionary covers only the
142
+ * UI around them (buttons, menus, pagination, confirmations).
143
+ */
144
+ interface AdminStrings {
145
+ loading: string;
146
+ loadingMetadata: string;
147
+ records: (total: number) => string;
148
+ searchPlaceholder: (plural: string) => string;
149
+ newRecord: string;
150
+ view: string;
151
+ edit: string;
152
+ clone: string;
153
+ remove: string;
154
+ copyId: string;
155
+ copyObject: string;
156
+ copyValue: string;
157
+ copied: string;
158
+ addToFilter: string;
159
+ openRelated: (label: string) => string;
160
+ filters: string;
161
+ clearFilters: string;
162
+ actions: string;
163
+ noRecords: string;
164
+ tryDifferentSearch: string;
165
+ deleteConfirm: (singular: string) => string;
166
+ page: (page: number, total: number) => string;
167
+ previous: string;
168
+ next: string;
169
+ cancel: string;
170
+ save: string;
171
+ saving: string;
172
+ create: string;
173
+ newTitle: (singular: string) => string;
174
+ editTitle: (singular: string) => string;
175
+ cloneTitle: (singular: string) => string;
176
+ entitiesFooter: (count: number) => string;
177
+ yes: string;
178
+ no: string;
179
+ searchMenu: string;
180
+ recents: string;
181
+ menus: string;
182
+ noMenuResults: string;
183
+ }
184
+ declare function stringsFor(locale: AdminLocale): AdminStrings;
185
+ declare const I18nProvider: react.Provider<AdminStrings>;
186
+ /** The active UI-string dictionary (set by `<MaestroAdmin locale>`; defaults to English). */
187
+ declare function useT(): AdminStrings;
188
+
189
+ interface EntityLabelOverride {
190
+ /** Singular label ("Usuário"). */
191
+ singular?: string;
192
+ /** Plural label ("Usuários") — also the sidebar entry. */
193
+ plural?: string;
194
+ /** Sidebar folder this entity belongs to (display name). Wins over heuristic prefix grouping. */
195
+ group?: string;
196
+ /** Field labels for this entity only, by field name. Wins over the global `fields` map. */
197
+ fields?: Record<string, string>;
198
+ }
199
+ /**
200
+ * Presentation-layer label rewrites, applied on top of the metadata the server serves. This is how a
201
+ * deployment localizes or humanizes auto-introspected names ("recoveryactions" → "Ações de
202
+ * Recuperação") without touching the engine: keys match either the entity `id` or its source `table`.
203
+ */
204
+ interface AdminLabels {
205
+ entities?: Record<string, EntityLabelOverride>;
206
+ /** Global field labels by field name (`createdAt` → "Criado em"), for every entity. */
207
+ fields?: Record<string, string>;
208
+ /** Display names for heuristic sidebar folders, by shared table-name prefix (`recovery` → "Recuperação"). */
209
+ groups?: Record<string, string>;
210
+ }
211
+ /** Returns a copy of `metadata` with every label override applied. No-op when `labels` is empty. */
212
+ declare function applyLabels(metadata: MaestroMetadata, labels: AdminLabels | undefined): MaestroMetadata;
213
+
131
214
  interface MaestroAdminProps {
132
215
  /** Base URL of the running maestro-server. Ignored when `client` is given. */
133
216
  apiUrl?: string;
@@ -137,6 +220,10 @@ interface MaestroAdminProps {
137
220
  client?: MaestroClient;
138
221
  /** Sidebar title. Defaults to "Maestro Admin". */
139
222
  title?: string;
223
+ /** UI-chrome language (buttons, menus, pagination). Defaults to `en`. */
224
+ locale?: AdminLocale;
225
+ /** Label rewrites (localization/humanization) applied over the served metadata. */
226
+ labels?: AdminLabels;
140
227
  }
141
228
  /**
142
229
  * The whole metadata-driven admin in one component. Point it at a running maestro-server and it reads
@@ -165,44 +252,99 @@ declare function useEntityList(entityId: string, query: ListQuery): AsyncState<L
165
252
  declare function useEntityRecord(entityId: string, id: string | undefined): AsyncState<globalThis.Record<string, unknown>>;
166
253
  /** Fields shown in the list table, ordered, honoring per-field list visibility. */
167
254
  declare function listFields(entity: EntityMetadata): FieldMetadata[];
255
+ /**
256
+ * The "principal" columns for an entity's table: instead of dumping every field, picks up to `max`
257
+ * of the most identifying ones — the display field first, then searchable/required texts, enums,
258
+ * relations, status booleans and dates — and drops blobs (`json`/`array`/`text`), sensitive values
259
+ * and the primary key. Everything remains visible in the detail view. When any field carries an
260
+ * explicit `list.order`/`list.width` (a curated schema), the curator's selection is honored as-is.
261
+ */
262
+ declare function primaryListFields(entity: EntityMetadata, max?: number): FieldMetadata[];
168
263
  /** Fields shown in the detail view, ordered. */
169
264
  declare function detailFields(entity: EntityMetadata): FieldMetadata[];
170
265
  /** Fields editable in the create/update form, ordered, per mode. */
171
266
  declare function formFields(entity: EntityMetadata, mode: 'create' | 'edit'): FieldMetadata[];
172
267
 
173
- declare function EntityList({ entity, onOpen, onCreate, onEdit, }: {
268
+ interface MenuGroup {
269
+ /** Stable key: the explicit group name, or the heuristic table-name prefix. */
270
+ id: string;
271
+ label: string;
272
+ entities: EntityMetadata[];
273
+ }
274
+ interface SidebarMenu {
275
+ groups: MenuGroup[];
276
+ /** Entities that belong to no group — rendered flat after the folders. */
277
+ ungrouped: EntityMetadata[];
278
+ }
279
+ /**
280
+ * Builds the sidebar menu structure: explicit groups first (an entity override's `group` in
281
+ * `AdminLabels` always wins), then heuristic clustering of what's left — collections sharing a
282
+ * table-name prefix (`recoveryactions`, `recoveryscores`, … → "Recovery") become a folder, since a
283
+ * shared prefix is how schemaless databases usually encode the module a collection belongs to.
284
+ * Heuristic folder names can be localized via `labels.groups` (prefix → display name). Everything
285
+ * else stays flat. Groups and their entities are sorted by label.
286
+ */
287
+ declare function buildMenu(entities: EntityMetadata[], labels?: AdminLabels): SidebarMenu;
288
+
289
+ /**
290
+ * Recently-visited entity ids, most recent first, persisted in `localStorage` so they survive
291
+ * reloads. `push` moves (or inserts) an id to the front, capped at 8. Pass a distinct `scope` (e.g.
292
+ * the API URL) when one browser origin hosts admins for several servers.
293
+ */
294
+ declare function useRecents(scope?: string): {
295
+ recents: string[];
296
+ push: (entityId: string) => void;
297
+ };
298
+
299
+ declare function EntityList({ entity, onOpen, onCreate, onEdit, onClone, onNavigate, initialFilters, }: {
174
300
  entity: EntityMetadata;
175
301
  onOpen: (id: string) => void;
176
302
  onCreate: () => void;
177
303
  onEdit: (id: string) => void;
304
+ onClone: (id: string) => void;
305
+ onNavigate: (entityId: string, id: string) => void;
306
+ initialFilters?: FilterInput[];
178
307
  }): react.JSX.Element;
179
308
 
180
- declare function EntityDetail({ entity, id, onBack, onEdit, }: {
309
+ declare function EntityDetail({ entity, id, onBack, onEdit, onClone, onDeleted, onNavigate, }: {
181
310
  entity: EntityMetadata;
182
311
  id: string;
183
312
  onBack: () => void;
184
313
  onEdit: () => void;
314
+ onClone: () => void;
315
+ onDeleted: () => void;
316
+ onNavigate: (entityId: string, id: string) => void;
185
317
  }): react.JSX.Element;
186
318
 
187
- declare function EntityForm({ entity, mode, id, onCancel, onSaved, }: {
319
+ declare function EntityForm({ entity, mode, id, cloneFromId, onCancel, onSaved, }: {
188
320
  entity: EntityMetadata;
189
321
  mode: 'create' | 'edit';
190
322
  id?: string;
323
+ /** In `create` mode, prefills the form from this record (minus the primary key and non-cloneable fields). */
324
+ cloneFromId?: string;
191
325
  onCancel: () => void;
192
326
  onSaved: (record: Record_) => void;
193
327
  }): react.JSX.Element;
194
328
 
195
- declare function Sidebar({ entities, activeId, onSelect, title, }: {
329
+ declare function Sidebar({ entities, activeId, onSelect, title, labels, recents, }: {
196
330
  entities: EntityMetadata[];
197
331
  activeId: string | undefined;
198
332
  onSelect: (entityId: string) => void;
199
333
  title: string;
334
+ /** Same object given to `<MaestroAdmin labels>` — supplies folder assignments and folder names. */
335
+ labels?: AdminLabels;
336
+ /** Recently-visited entity ids, most recent first. */
337
+ recents?: string[];
200
338
  }): react.JSX.Element;
201
339
 
202
- /** Renders a single record value read-only, shaped by the field's type (for tables and detail views). */
203
- declare function FieldValue({ field, value }: {
340
+ /**
341
+ * Renders a single record value read-only, shaped by the field's type (for tables and detail views).
342
+ * `compact` is the table-cell mode: long text truncates instead of stretching the row.
343
+ */
344
+ declare function FieldValue({ field, value, compact }: {
204
345
  field: FieldMetadata;
205
346
  value: unknown;
347
+ compact?: boolean;
206
348
  }): react.JSX.Element;
207
349
 
208
350
  /**
@@ -217,4 +359,4 @@ declare function FieldInput({ field, value, onChange, disabled, }: {
217
359
  disabled?: boolean;
218
360
  }): react.JSX.Element;
219
361
 
220
- export { AdminClientProvider, type AsyncState, type EntityCapabilities, EntityDetail, EntityForm, EntityList, type EntityMetadata, FieldInput, type FieldMetadata, FieldValue, type ListQuery, type ListResult, MaestroAdmin, type MaestroAdminProps, MaestroApiError, MaestroClient, type MaestroClientConfig, type MaestroMetadata, Sidebar, detailFields, formFields, listFields, useAsync, useClient, useEntityList, useEntityRecord, useMetadata };
362
+ export { AdminClientProvider, type AdminLabels, type AdminLocale, type AdminStrings, type AsyncState, type EntityCapabilities, EntityDetail, EntityForm, type EntityLabelOverride, EntityList, type EntityMetadata, FieldInput, type FieldMetadata, FieldValue, type FilterInput, I18nProvider, type ListQuery, type ListResult, MaestroAdmin, type MaestroAdminProps, MaestroApiError, MaestroClient, type MaestroClientConfig, type MaestroMetadata, type MenuGroup, Sidebar, type SidebarMenu, applyLabels, buildMenu, detailFields, formFields, listFields, primaryListFields, stringsFor, useAsync, useClient, useEntityList, useEntityRecord, useMetadata, useRecents, useT };