@firecms/collection_editor 3.0.0 → 3.1.0-canary.02232f4

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 (119) hide show
  1. package/dist/ConfigControllerProvider.d.ts +6 -0
  2. package/dist/api/generateCollectionApi.d.ts +71 -0
  3. package/dist/api/index.d.ts +1 -0
  4. package/dist/index.d.ts +5 -1
  5. package/dist/index.es.js +15234 -8138
  6. package/dist/index.es.js.map +1 -1
  7. package/dist/index.umd.js +15199 -8103
  8. package/dist/index.umd.js.map +1 -1
  9. package/dist/locales/de.d.ts +120 -0
  10. package/dist/locales/en.d.ts +120 -0
  11. package/dist/locales/es.d.ts +120 -0
  12. package/dist/locales/fr.d.ts +120 -0
  13. package/dist/locales/hi.d.ts +120 -0
  14. package/dist/locales/it.d.ts +120 -0
  15. package/dist/locales/pt.d.ts +120 -0
  16. package/dist/types/collection_editor_controller.d.ts +14 -0
  17. package/dist/types/collection_inference.d.ts +8 -2
  18. package/dist/types/config_controller.d.ts +31 -1
  19. package/dist/ui/AddKanbanColumnAction.d.ts +11 -0
  20. package/dist/ui/KanbanSetupAction.d.ts +10 -0
  21. package/dist/ui/collection_editor/AICollectionGeneratorPopover.d.ts +37 -0
  22. package/dist/ui/collection_editor/AIModifiedPathsContext.d.ts +20 -0
  23. package/dist/ui/collection_editor/CollectionDetailsForm.d.ts +2 -3
  24. package/dist/ui/collection_editor/CollectionEditorDialog.d.ts +24 -0
  25. package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +4 -1
  26. package/dist/ui/collection_editor/CollectionJsonImportDialog.d.ts +7 -0
  27. package/dist/ui/collection_editor/CollectionYupValidation.d.ts +9 -13
  28. package/dist/ui/collection_editor/DisplaySettingsForm.d.ts +3 -0
  29. package/dist/ui/collection_editor/EntityActionsEditTab.d.ts +2 -1
  30. package/dist/ui/collection_editor/ExtendSettingsForm.d.ts +14 -0
  31. package/dist/ui/collection_editor/GeneralSettingsForm.d.ts +7 -0
  32. package/dist/ui/collection_editor/KanbanConfigSection.d.ts +4 -0
  33. package/dist/ui/collection_editor/PropertyEditView.d.ts +6 -1
  34. package/dist/ui/collection_editor/PropertyTree.d.ts +2 -1
  35. package/dist/ui/collection_editor/SubcollectionsEditTab.d.ts +2 -1
  36. package/dist/ui/collection_editor/ViewModeSwitch.d.ts +6 -0
  37. package/dist/ui/collection_editor/properties/EnumPropertyField.d.ts +2 -1
  38. package/dist/ui/collection_editor/properties/conditions/ConditionsEditor.d.ts +10 -0
  39. package/dist/ui/collection_editor/properties/conditions/ConditionsPanel.d.ts +2 -0
  40. package/dist/ui/collection_editor/properties/conditions/EnumConditionsEditor.d.ts +6 -0
  41. package/dist/ui/collection_editor/properties/conditions/index.d.ts +6 -0
  42. package/dist/ui/collection_editor/properties/conditions/property_paths.d.ts +19 -0
  43. package/dist/useCollectionEditorPlugin.d.ts +7 -1
  44. package/dist/utils/validateCollectionJson.d.ts +22 -0
  45. package/package.json +15 -15
  46. package/src/ConfigControllerProvider.tsx +82 -47
  47. package/src/api/generateCollectionApi.ts +119 -0
  48. package/src/api/index.ts +1 -0
  49. package/src/index.ts +28 -1
  50. package/src/locales/de.ts +125 -0
  51. package/src/locales/en.ts +145 -0
  52. package/src/locales/es.ts +125 -0
  53. package/src/locales/fr.ts +125 -0
  54. package/src/locales/hi.ts +125 -0
  55. package/src/locales/it.ts +125 -0
  56. package/src/locales/pt.ts +125 -0
  57. package/src/types/collection_editor_controller.tsx +16 -3
  58. package/src/types/collection_inference.ts +15 -2
  59. package/src/types/config_controller.tsx +37 -1
  60. package/src/ui/AddKanbanColumnAction.tsx +203 -0
  61. package/src/ui/EditorCollectionAction.tsx +3 -3
  62. package/src/ui/EditorCollectionActionStart.tsx +1 -2
  63. package/src/ui/EditorEntityAction.tsx +3 -2
  64. package/src/ui/HomePageEditorCollectionAction.tsx +41 -13
  65. package/src/ui/KanbanSetupAction.tsx +38 -0
  66. package/src/ui/MissingReferenceWidget.tsx +1 -1
  67. package/src/ui/NewCollectionButton.tsx +4 -2
  68. package/src/ui/NewCollectionCard.tsx +7 -4
  69. package/src/ui/PropertyAddColumnComponent.tsx +4 -3
  70. package/src/ui/collection_editor/AICollectionGeneratorPopover.tsx +243 -0
  71. package/src/ui/collection_editor/AIModifiedPathsContext.tsx +88 -0
  72. package/src/ui/collection_editor/CollectionDetailsForm.tsx +222 -268
  73. package/src/ui/collection_editor/CollectionEditorDialog.tsx +270 -204
  74. package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +138 -71
  75. package/src/ui/collection_editor/CollectionJsonImportDialog.tsx +171 -0
  76. package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +202 -101
  77. package/src/ui/collection_editor/DisplaySettingsForm.tsx +335 -0
  78. package/src/ui/collection_editor/EntityActionsEditTab.tsx +106 -97
  79. package/src/ui/collection_editor/EntityActionsSelectDialog.tsx +8 -10
  80. package/src/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +5 -7
  81. package/src/ui/collection_editor/EnumForm.tsx +153 -102
  82. package/src/ui/collection_editor/ExtendSettingsForm.tsx +94 -0
  83. package/src/ui/collection_editor/GeneralSettingsForm.tsx +335 -0
  84. package/src/ui/collection_editor/GetCodeDialog.tsx +63 -41
  85. package/src/ui/collection_editor/KanbanConfigSection.tsx +209 -0
  86. package/src/ui/collection_editor/LayoutModeSwitch.tsx +27 -43
  87. package/src/ui/collection_editor/PropertyEditView.tsx +272 -199
  88. package/src/ui/collection_editor/PropertyFieldPreview.tsx +1 -1
  89. package/src/ui/collection_editor/PropertyTree.tsx +130 -58
  90. package/src/ui/collection_editor/SubcollectionsEditTab.tsx +169 -163
  91. package/src/ui/collection_editor/UnsavedChangesDialog.tsx +0 -2
  92. package/src/ui/collection_editor/ViewModeSwitch.tsx +43 -0
  93. package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +6 -3
  94. package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +5 -2
  95. package/src/ui/collection_editor/properties/BlockPropertyField.tsx +0 -2
  96. package/src/ui/collection_editor/properties/BooleanPropertyField.tsx +4 -1
  97. package/src/ui/collection_editor/properties/CommonPropertyFields.tsx +6 -4
  98. package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +126 -42
  99. package/src/ui/collection_editor/properties/EnumPropertyField.tsx +32 -24
  100. package/src/ui/collection_editor/properties/MapPropertyField.tsx +8 -9
  101. package/src/ui/collection_editor/properties/MarkdownPropertyField.tsx +128 -53
  102. package/src/ui/collection_editor/properties/NumberPropertyField.tsx +3 -1
  103. package/src/ui/collection_editor/properties/ReferencePropertyField.tsx +6 -9
  104. package/src/ui/collection_editor/properties/StoragePropertyField.tsx +65 -49
  105. package/src/ui/collection_editor/properties/StringPropertyField.tsx +3 -1
  106. package/src/ui/collection_editor/properties/UrlPropertyField.tsx +12 -10
  107. package/src/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +23 -4
  108. package/src/ui/collection_editor/properties/conditions/ConditionsEditor.tsx +866 -0
  109. package/src/ui/collection_editor/properties/conditions/ConditionsPanel.tsx +28 -0
  110. package/src/ui/collection_editor/properties/conditions/EnumConditionsEditor.tsx +599 -0
  111. package/src/ui/collection_editor/properties/conditions/index.ts +6 -0
  112. package/src/ui/collection_editor/properties/conditions/property_paths.ts +92 -0
  113. package/src/ui/collection_editor/properties/validation/ArrayPropertyValidation.tsx +5 -2
  114. package/src/ui/collection_editor/properties/validation/GeneralPropertyValidation.tsx +7 -5
  115. package/src/ui/collection_editor/properties/validation/NumberPropertyValidation.tsx +10 -7
  116. package/src/ui/collection_editor/properties/validation/StringPropertyValidation.tsx +11 -9
  117. package/src/ui/collection_editor/properties/validation/ValidationPanel.tsx +5 -2
  118. package/src/useCollectionEditorPlugin.tsx +53 -22
  119. package/src/utils/validateCollectionJson.ts +380 -0
@@ -1,5 +1,5 @@
1
- import React, { useEffect, useState } from "react";
2
- import { EntityCollection, FieldCaption, IconForView, SearchIconsView, singular, toSnakeCase, } from "@firecms/core";
1
+ import React, { useMemo, useState } from "react";
2
+ import { EntityCollection, FieldCaption, getFieldConfig, IconForView, Property, PropertyConfigBadge, resolveCollection, SearchIconsView, singular, toSnakeCase, unslugify, useAuthController, useCustomizationController, useTranslation } from "@firecms/core";
3
3
  import {
4
4
  BooleanSwitchWithLabel,
5
5
  Chip,
@@ -8,11 +8,10 @@ import {
8
8
  Container,
9
9
  DebouncedTextField,
10
10
  Dialog,
11
- ExpandablePanel,
11
+ HistoryIcon,
12
12
  IconButton,
13
13
  Select,
14
14
  SelectItem,
15
- SettingsIcon,
16
15
  TextField,
17
16
  Tooltip,
18
17
  Typography,
@@ -22,16 +21,19 @@ import {
22
21
  import { Field, getIn, useFormex } from "@firecms/formex";
23
22
  import { useCollectionEditorController } from "../../useCollectionEditorController";
24
23
  import { LayoutModeSwitch } from "./LayoutModeSwitch";
24
+ import { ViewModeSwitch } from "./ViewModeSwitch";
25
+ import { KanbanConfigSection } from "./KanbanConfigSection";
26
+ import { PropertyFormDialog } from "./PropertyEditView";
25
27
 
26
28
  export function CollectionDetailsForm({
27
- isNewCollection,
28
- reservedGroups,
29
- existingPaths,
30
- existingIds,
31
- groups,
32
- parentCollection,
33
- children
34
- }: {
29
+ isNewCollection,
30
+ reservedGroups,
31
+ existingPaths,
32
+ existingIds,
33
+ groups,
34
+ parentCollection,
35
+ expandKanban
36
+ }: {
35
37
  isNewCollection: boolean,
36
38
  reservedGroups?: string[];
37
39
  existingPaths?: string[];
@@ -39,7 +41,7 @@ export function CollectionDetailsForm({
39
41
  groups: string[] | null;
40
42
  parentCollection?: EntityCollection;
41
43
  parentCollectionIds?: string[];
42
- children?: React.ReactNode;
44
+ expandKanban?: boolean;
43
45
  }) {
44
46
 
45
47
  const groupRef = React.useRef<HTMLInputElement>(null);
@@ -57,7 +59,37 @@ export function CollectionDetailsForm({
57
59
  const collectionEditor = useCollectionEditorController();
58
60
 
59
61
  const [iconDialogOpen, setIconDialogOpen] = useState(false);
60
- const [advancedPanelExpanded, setAdvancedPanelExpanded] = useState(false);
62
+ const [orderPropertyDialogOpen, setOrderPropertyDialogOpen] = useState(false);
63
+
64
+ const { t } = useTranslation();
65
+
66
+ const authController = useAuthController();
67
+ const customizationController = useCustomizationController();
68
+
69
+ // Resolve collection to get properties for order property select
70
+ const resolvedCollection = useMemo(() => resolveCollection({
71
+ collection: values,
72
+ path: values.path,
73
+ propertyConfigs: customizationController.propertyConfigs,
74
+ authController
75
+ }), [values, customizationController.propertyConfigs, authController]);
76
+
77
+ // Get number properties (for orderProperty)
78
+ const numberProperties = useMemo(() => {
79
+ const result: { key: string; label: string; property: Property; }[] = [];
80
+ if (!resolvedCollection.properties) return result;
81
+
82
+ Object.entries(resolvedCollection.properties).forEach(([key, prop]) => {
83
+ if (prop && 'dataType' in prop && prop.dataType === 'number') {
84
+ result.push({
85
+ key,
86
+ label: (prop as Property).name || key,
87
+ property: prop as Property
88
+ });
89
+ }
90
+ });
91
+ return result;
92
+ }, [resolvedCollection.properties]);
61
93
 
62
94
  const updateDatabaseId = (databaseId: string) => {
63
95
  setFieldValue("databaseId", databaseId ?? undefined);
@@ -83,13 +115,7 @@ export function CollectionDetailsForm({
83
115
 
84
116
  };
85
117
 
86
- useEffect(() => {
87
- if (errors.id) {
88
- setAdvancedPanelExpanded(true);
89
- }
90
- }, [errors.id]);
91
-
92
- const collectionIcon = <IconForView collectionOrView={values}/>;
118
+ const collectionIcon = <IconForView collectionOrView={values} />;
93
119
 
94
120
  const groupOptions = groups?.filter((group) => !reservedGroups?.includes(group));
95
121
 
@@ -103,17 +129,6 @@ export function CollectionDetailsForm({
103
129
 
104
130
  const isSubcollection = !!parentCollection;
105
131
 
106
- let customIdValue: "true" | "false" | "optional" | "code_defined" | undefined;
107
- if (typeof values.customId === "object") {
108
- customIdValue = "code_defined";
109
- } else if (values.customId === true) {
110
- customIdValue = "true";
111
- } else if (values.customId === false) {
112
- customIdValue = "false";
113
- } else if (values.customId === "optional") {
114
- customIdValue = "optional";
115
- }
116
-
117
132
  const showErrors = submitCount > 0;
118
133
 
119
134
  return (
@@ -124,13 +139,13 @@ export function CollectionDetailsForm({
124
139
  <div
125
140
  className="flex flex-row gap-2 py-2 pt-3 items-center">
126
141
  <Typography variant={!isNewCollection ? "h5" : "h4"} className={"flex-grow"}>
127
- {isNewCollection ? "New collection" : `${values?.name} collection`}
142
+ {isNewCollection ? t("new_collection") : t("collection_with_name", { name: values?.name || "" })}
128
143
  </Typography>
129
144
  <DefaultDatabaseField databaseId={values.databaseId}
130
- onDatabaseIdUpdate={updateDatabaseId}/>
145
+ onDatabaseIdUpdate={updateDatabaseId} />
131
146
 
132
- <Tooltip title={"Change icon"}
133
- asChild={true}>
147
+ <Tooltip title={t("change_icon")}
148
+ asChild={true}>
134
149
  <IconButton
135
150
  shape={"square"}
136
151
  onClick={() => setIconDialogOpen(true)}>
@@ -141,7 +156,7 @@ export function CollectionDetailsForm({
141
156
 
142
157
  {parentCollection && <Chip colorScheme={"tealDarker"}>
143
158
  <Typography variant={"caption"}>
144
- This is a subcollection of <b>{parentCollection.name}</b>
159
+ {t("is_subcollection_of")} <b>{parentCollection.name}</b>
145
160
  </Typography>
146
161
  </Chip>}
147
162
 
@@ -152,27 +167,26 @@ export function CollectionDetailsForm({
152
167
  <TextField
153
168
  value={values.name ?? ""}
154
169
  onChange={(e: any) => updateName(e.target.value)}
155
- label={"Name"}
170
+ label={t("name")}
156
171
  autoFocus={true}
157
172
  required
158
- error={showErrors && Boolean(errors.name)}/>
173
+ error={showErrors && Boolean(errors.name)} />
159
174
  <FieldCaption error={touched.name && Boolean(errors.name)}>
160
- {touched.name && Boolean(errors.name) ? errors.name : "Name of this collection, usually a plural name (e.g. Products)"}
175
+ {touched.name && Boolean(errors.name) ? errors.name : t("collection_name_description")}
161
176
  </FieldCaption>
162
177
  </div>
163
178
 
164
179
  <div className={cls("col-span-12 ")}>
165
180
  <Field name={"path"}
166
- as={DebouncedTextField}
167
- label={"Path"}
168
- disabled={!isNewCollection}
169
- required
170
- error={showErrors && Boolean(errors.path)}/>
181
+ as={DebouncedTextField}
182
+ label={t("path")}
183
+ required
184
+ error={showErrors && Boolean(errors.path)} />
171
185
 
172
186
  <FieldCaption error={touched.path && Boolean(errors.path)}>
173
187
  {touched.path && Boolean(errors.path)
174
188
  ? errors.path
175
- : isSubcollection ? "Relative path to the parent (no need to include the parent path)" : "Path that this collection is stored in, in the database"}
189
+ : isSubcollection ? t("relative_path_to_parent") : t("path_in_database")}
176
190
  </FieldCaption>
177
191
 
178
192
  </div>
@@ -215,236 +229,174 @@ export function CollectionDetailsForm({
215
229
  <LayoutModeSwitch
216
230
  className={"col-span-12"}
217
231
  value={values.openEntityMode ?? "side_panel"}
218
- onChange={(value) => setFieldValue("openEntityMode", value)}/>
232
+ onChange={(value) => setFieldValue("openEntityMode", value)} />
219
233
 
220
- <div className={"col-span-12"}>
221
- <BooleanSwitchWithLabel
222
- position={"start"}
223
- size={"large"}
224
- allowIndeterminate={true}
225
- label={values.history === null || values.history === undefined ? "Document history revisions enabled if enabled globally" : (
226
- values.history ? "Document history revisions ENABLED" : "Document history revisions NOT enabled"
227
- )}
228
- onValueChange={(v) => setFieldValue("history", v)}
229
- value={values.history === undefined ? null : values.history}
230
- />
231
- <FieldCaption>
232
- When enabled, each document in this collection will have a history of changes.
233
- This is useful for auditing purposes. The data is stored in a subcollection of the document
234
- in your database, called <b>__history</b>.
235
- </FieldCaption>
236
- </div>
234
+ <ViewModeSwitch
235
+ className={"col-span-12"}
236
+ value={values.defaultViewMode ?? "table"}
237
+ onChange={(value) => setFieldValue("defaultViewMode", value)} />
237
238
 
239
+ <KanbanConfigSection className={"col-span-12"} forceExpanded={expandKanban} />
238
240
 
239
- <div className={"col-span-12 mt-8"}>
240
- <ExpandablePanel
241
- expanded={advancedPanelExpanded}
242
- onExpandedChange={setAdvancedPanelExpanded}
243
- title={
244
- <div className="flex flex-row text-surface-500">
245
- <SettingsIcon/>
246
- <Typography variant={"subtitle2"}
247
- className="ml-2">
248
- Advanced
249
- </Typography>
250
- </div>}
251
- initiallyExpanded={false}>
252
- <div className={"grid grid-cols-12 gap-4 p-4"}>
253
-
254
- <div className={"col-span-12"}>
255
- <Field name={"id"}
256
- as={DebouncedTextField}
257
- disabled={!isNewCollection}
258
- label={"Collection id"}
259
- error={showErrors && Boolean(errors.id)}/>
260
- <FieldCaption error={touched.id && Boolean(errors.id)}>
261
- {touched.id && Boolean(errors.id) ? errors.id : "This id identifies this collection. Typically the same as the path."}
262
- </FieldCaption>
263
- </div>
264
-
265
- <div className={"col-span-12"}>
266
- <TextField
267
- error={showErrors && Boolean(errors.singularName)}
268
- name={"singularName"}
269
- aria-describedby={"singularName-helper"}
270
- onChange={(e) => {
271
- setFieldTouched("singularName", true);
272
- return handleChange(e);
273
- }}
274
- value={values.singularName ?? ""}
275
- label={"Singular name"}/>
276
- <FieldCaption error={showErrors && Boolean(errors.singularName)}>
277
- {showErrors && Boolean(errors.singularName) ? errors.singularName : "Optionally define a singular name for your entities"}
278
- </FieldCaption>
279
- </div>
280
- <div className={"col-span-12"}>
281
- <TextField
282
- error={showErrors && Boolean(errors.sideDialogWidth)}
283
- name={"sideDialogWidth"}
284
- type={"number"}
285
- aria-describedby={"sideDialogWidth-helper"}
286
- onChange={(e) => {
287
- setFieldTouched("sideDialogWidth", true);
288
- const value = e.target.value;
289
- if (!value) {
290
- setFieldValue("sideDialogWidth", null);
291
- } else if (!isNaN(Number(value))) {
292
- setFieldValue("sideDialogWidth", Number(value));
293
- }
294
- }}
295
- endAdornment={<IconButton
296
- size={"small"}
297
- onClick={() => {
298
- setFieldValue("sideDialogWidth", null);
299
- }}
300
- disabled={!values.sideDialogWidth}>
301
- <CloseIcon size={"small"}/>
302
- </IconButton>}
303
- value={values.sideDialogWidth ?? ""}
304
- label={"Side dialog width"}/>
305
- <FieldCaption error={showErrors && Boolean(errors.singularName)}>
306
- {showErrors && Boolean(errors.singularName) ? errors.singularName : "Optionally define the width (in pixels) of entities side dialog. Default is 768px"}
307
- </FieldCaption>
308
- </div>
309
- <div className={"col-span-12"}>
310
- <TextField
311
- error={showErrors && Boolean(errors.description)}
312
- name="description"
313
- value={values.description ?? ""}
314
- onChange={handleChange}
315
- multiline
316
- minRows={2}
317
- aria-describedby="description-helper-text"
318
- label="Description"
319
- />
320
- <FieldCaption error={showErrors && Boolean(errors.description)}>
321
- {showErrors && Boolean(errors.description) ? errors.description : "Description of the collection, you can use markdown"}
322
- </FieldCaption>
323
- </div>
241
+ <div className={"col-span-12 mt-4"}>
242
+ {(() => {
243
+ // Check if orderProperty references a non-existent property
244
+ const orderPropertyMissing = Boolean(values.orderProperty) &&
245
+ !numberProperties.some(p => p.key === values.orderProperty);
324
246
 
325
- <div className={"col-span-12"}>
247
+ return (
248
+ <>
326
249
  <Select
327
- name="defaultSize"
250
+ key={`order-select-${numberProperties.length}`}
251
+ name="orderProperty"
252
+ label={t("order_property")}
328
253
  size={"large"}
329
254
  fullWidth={true}
330
- label="Default row size"
331
255
  position={"item-aligned"}
332
- onChange={handleChange}
333
- value={values.defaultSize ?? ""}
334
- renderValue={(value: any) => value.toUpperCase()}
335
- >
336
- {["xs", "s", "m", "l", "xl"].map((value) => (
337
- <SelectItem
338
- key={`size-select-${value}`}
339
- value={value}>
340
- {value.toUpperCase()}
341
- </SelectItem>
342
- ))}
343
- </Select>
344
- </div>
345
-
346
- <div className={"col-span-12"}>
347
- <BooleanSwitchWithLabel
348
- position={"start"}
349
- size={"large"}
350
- label={values.includeJsonView === undefined || values.includeJsonView ? "Include JSON view" : "Do not include JSON view"}
351
- onValueChange={(v) => setFieldValue("includeJsonView", v)}
352
- value={values.includeJsonView === undefined ? true : values.includeJsonView}
353
- />
354
- <FieldCaption>
355
- Include the JSON representation of the document.
356
- </FieldCaption>
357
- </div>
358
-
359
- <div className={"col-span-12"}>
360
- <BooleanSwitchWithLabel
361
- position={"start"}
362
- size={"large"}
363
- label={values.inlineEditing === undefined || values.inlineEditing ? "Data can be edited directly in the table view" : "Data can be edited only in the form view"}
364
- onValueChange={(v) => setFieldValue("inlineEditing", v)}
365
- value={values.inlineEditing === undefined ? true : values.inlineEditing}
366
- />
367
- <FieldCaption>
368
- Allow editing data directly in the table view, without opening the form view.
369
- </FieldCaption>
370
- </div>
371
-
372
- <div className={"col-span-12"}>
373
- <Select
374
- name="customId"
375
- label="Document IDs generation"
376
- position={"item-aligned"}
377
- size={"large"}
378
- fullWidth={true}
379
- disabled={customIdValue === "code_defined"}
256
+ disabled={numberProperties.length === 0}
257
+ error={orderPropertyMissing}
258
+ value={values.orderProperty ?? ""}
380
259
  onValueChange={(v) => {
381
- if (v === "code_defined")
382
- throw new Error("This should not happen");
383
- setFieldValue("customId", v);
260
+ setFieldValue("orderProperty", v || undefined);
384
261
  }}
385
- value={customIdValue ?? ""}
386
- renderValue={(value: any) => {
387
- if (value === "code_defined")
388
- return "Code defined";
389
- else if (value === "true")
390
- return "Users must define an ID";
391
- else if (value === "optional")
392
- return "Users can define an ID, but it is not required";
393
- else
394
- return "Document ID is generated automatically";
262
+ renderValue={(value) => {
263
+ if (orderPropertyMissing) {
264
+ return <span className="text-red-500">{value} ({t("not_found_suffix")})</span>;
265
+ }
266
+ const prop = numberProperties.find(p => p.key === value);
267
+ if (!prop) return t("select_a_property");
268
+ const fieldConfig = getFieldConfig(prop.property, customizationController.propertyConfigs);
269
+ return (
270
+ <div className="flex items-center gap-2">
271
+ <PropertyConfigBadge propertyConfig={fieldConfig} />
272
+ <span>{prop.label}</span>
273
+ </div>
274
+ );
395
275
  }}
276
+ endAdornment={values.orderProperty ? (
277
+ <IconButton
278
+ size="small"
279
+ onClick={(e) => {
280
+ e.stopPropagation();
281
+ setFieldValue("orderProperty", undefined);
282
+ }}
283
+ >
284
+ <CloseIcon size="small" />
285
+ </IconButton>
286
+ ) : undefined}
396
287
  >
397
- <SelectItem value={"false"}>
398
- Document ID is generated automatically
399
- </SelectItem>
400
- <SelectItem value={"true"}>
401
- Users must define an ID
402
- </SelectItem>
403
- <SelectItem value={"optional"}>
404
- Users can define an ID, but it is not required
405
- </SelectItem>
288
+ {numberProperties.map((prop) => {
289
+ const fieldConfig = getFieldConfig(prop.property, customizationController.propertyConfigs);
290
+ return (
291
+ <SelectItem key={prop.key} value={prop.key}>
292
+ <div className="flex items-center gap-3">
293
+ <PropertyConfigBadge propertyConfig={fieldConfig} />
294
+ <div>
295
+ <div>{prop.label}</div>
296
+ <Typography variant="caption" color="secondary">
297
+ {fieldConfig?.name || t("number")}
298
+ </Typography>
299
+ </div>
300
+ </div>
301
+ </SelectItem>
302
+ );
303
+ })}
406
304
  </Select>
407
- </div>
408
- <div className={"col-span-12 mt-4"}>
409
- <BooleanSwitchWithLabel
410
- position={"start"}
411
- size={"large"}
412
- label="Collection group"
413
- onValueChange={(v) => setFieldValue("collectionGroup", v)}
414
- value={values.collectionGroup ?? false}
415
- />
416
- <FieldCaption>
417
- A collection group consists of all collections with the same path. This allows
418
- you
419
- to query over multiple collections at once.
305
+ <FieldCaption error={orderPropertyMissing}>
306
+ {orderPropertyMissing
307
+ ? t("order_property_not_found", { property: values.orderProperty ?? "" })
308
+ : numberProperties.length === 0
309
+ ? t("no_number_properties")
310
+ : t("order_property_description")
311
+ }
420
312
  </FieldCaption>
421
- </div>
422
- <div className={"col-span-12"}>
423
- <BooleanSwitchWithLabel
424
- position={"start"}
425
- size={"large"}
426
- label="Enable text search for this collection"
427
- onValueChange={(v) => setFieldValue("textSearchEnabled", v)}
428
- value={values.textSearchEnabled ?? false}
313
+ </>
314
+ );
315
+ })()}
316
+ {(() => {
317
+ // Check if orderProperty references a non-existent property
318
+ const orderPropertyMissing = Boolean(values.orderProperty) &&
319
+ !numberProperties.some(p => p.key === values.orderProperty);
320
+ const showCreateButton = !values.orderProperty || orderPropertyMissing;
321
+
322
+ // Pre-fill with missing property id or default "__order"
323
+ const dialogPropertyKey = orderPropertyMissing && values.orderProperty
324
+ ? values.orderProperty
325
+ : "__order";
326
+ const dialogPropertyName = orderPropertyMissing && values.orderProperty
327
+ ? unslugify(values.orderProperty)
328
+ : t("order_label");
329
+
330
+ if (!showCreateButton) return null;
331
+
332
+ return (
333
+ <>
334
+ <button
335
+ type="button"
336
+ className="ml-3.5 text-sm text-primary hover:text-primary-dark mt-2"
337
+ onClick={() => setOrderPropertyDialogOpen(true)}
338
+ >
339
+ {t("create_property", { property: dialogPropertyKey })}
340
+ </button>
341
+ <PropertyFormDialog
342
+ open={orderPropertyDialogOpen}
343
+ onCancel={() => setOrderPropertyDialogOpen(false)}
344
+ property={{
345
+ dataType: "number",
346
+ name: dialogPropertyName,
347
+ disabled: true,
348
+ hideFromCollection: true
349
+ }}
350
+ propertyKey={dialogPropertyKey}
351
+ existingProperty={false}
352
+ autoOpenTypeSelect={false}
353
+ autoUpdateId={false}
354
+ inArray={false}
355
+ allowDataInference={false}
356
+ propertyConfigs={customizationController.propertyConfigs}
357
+ collectionEditable={true}
358
+ existingPropertyKeys={Object.keys(values.properties ?? {})}
359
+ onPropertyChanged={({ id, property }) => {
360
+ const newProperties = {
361
+ ...values.properties,
362
+ [id!]: property
363
+ };
364
+ const newPropertiesOrder = [...(values.propertiesOrder ?? Object.keys(values.properties ?? {})), id];
365
+ setFieldValue("properties", newProperties);
366
+ setFieldValue("propertiesOrder", newPropertiesOrder);
367
+ setFieldValue("orderProperty", id);
368
+ setOrderPropertyDialogOpen(false);
369
+ }}
429
370
  />
430
- <FieldCaption>
431
- Allow text search for this collection. If you have not specified a text search
432
- delegate, this will use the built-in local text search. This is not recommended
433
- for large collections, as it may incur in performance and cost issues.
434
- </FieldCaption>
435
- </div>
371
+ </>
372
+ );
373
+ })()}
374
+ </div>
436
375
 
376
+ <div className={"col-span-12"}>
377
+ <BooleanSwitchWithLabel
378
+ position={"start"}
379
+ size={"large"}
380
+ allowIndeterminate={true}
381
+ label={<span className="flex items-center gap-2"><HistoryIcon size={"smallest"} />{values.history === null || values.history === undefined ? t("doc_history_global") : (
382
+ values.history ? t("doc_history_enabled") : t("doc_history_not_enabled")
383
+ )}</span>}
384
+ onValueChange={(v) => setFieldValue("history", v)}
385
+ value={values.history === undefined ? null : values.history}
386
+ />
387
+ <FieldCaption>
388
+ {t("doc_history_description")}
389
+ </FieldCaption>
390
+ </div>
437
391
 
438
- </div>
439
- </ExpandablePanel>
440
392
 
441
- {children}
393
+ <div className={"col-span-12 mt-8"}>
442
394
 
443
395
  </div>
444
396
 
445
397
  </div>
446
398
 
447
- <div style={{ height: "52px" }}/>
399
+ <div style={{ height: "52px" }} />
448
400
 
449
401
  <Dialog
450
402
  open={iconDialogOpen}
@@ -454,10 +406,10 @@ export function CollectionDetailsForm({
454
406
  >
455
407
  <div className={"p-4 overflow-auto min-h-[200px]"}>
456
408
  <SearchIconsView selectedIcon={typeof values.icon === "string" ? values.icon : undefined}
457
- onIconSelected={(icon: string) => {
458
- setIconDialogOpen(false);
459
- setFieldValue("icon", icon);
460
- }}/>
409
+ onIconSelected={(icon: string) => {
410
+ setIconDialogOpen(false);
411
+ setFieldValue("icon", icon);
412
+ }} />
461
413
  </div>
462
414
 
463
415
  </Dialog>
@@ -468,18 +420,20 @@ export function CollectionDetailsForm({
468
420
  }
469
421
 
470
422
  function DefaultDatabaseField({
471
- databaseId,
472
- onDatabaseIdUpdate
473
- }: { databaseId?: string, onDatabaseIdUpdate: (databaseId: string) => void }) {
423
+ databaseId,
424
+ onDatabaseIdUpdate
425
+ }: { databaseId?: string, onDatabaseIdUpdate: (databaseId: string) => void }) {
426
+
427
+ const { t } = useTranslation();
474
428
 
475
- return <Tooltip title={"Database ID"}
476
- side={"top"}
477
- align={"start"}>
429
+ return <Tooltip title={t("database_id")}
430
+ side={"top"}
431
+ align={"start"}>
478
432
  <TextField size={"small"}
479
- invisible={true}
480
- inputClassName={"text-end"}
481
- value={databaseId ?? ""}
482
- onChange={(e: any) => onDatabaseIdUpdate(e.target.value)}
483
- placeholder={"(default)"}></TextField>
433
+ invisible={true}
434
+ inputClassName={"text-end"}
435
+ value={databaseId ?? ""}
436
+ onChange={(e: any) => onDatabaseIdUpdate(e.target.value)}
437
+ placeholder={t("default_text")}></TextField>
484
438
  </Tooltip>
485
439
  }