@open-mercato/core 0.6.6-develop.6515.1.01911d00e2 → 0.6.6-develop.6519.1.bdc91fb755

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 (86) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/configs/lib/upgrade-actions.js +31 -1
  3. package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
  4. package/dist/modules/customers/ai-tools/activities-tasks-pack.js +11 -2
  5. package/dist/modules/customers/ai-tools/activities-tasks-pack.js.map +2 -2
  6. package/dist/modules/customers/api/companies/[id]/route.js +3 -2
  7. package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
  8. package/dist/modules/customers/api/dictionaries/context.js +4 -2
  9. package/dist/modules/customers/api/dictionaries/context.js.map +2 -2
  10. package/dist/modules/customers/api/interactions/conflicts/route.js +2 -1
  11. package/dist/modules/customers/api/interactions/conflicts/route.js.map +2 -2
  12. package/dist/modules/customers/api/interactions/counts/route.js +7 -2
  13. package/dist/modules/customers/api/interactions/counts/route.js.map +2 -2
  14. package/dist/modules/customers/api/people/[id]/route.js +3 -2
  15. package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
  16. package/dist/modules/customers/cli.js +19 -0
  17. package/dist/modules/customers/cli.js.map +2 -2
  18. package/dist/modules/customers/commands/interactions.js +8 -3
  19. package/dist/modules/customers/commands/interactions.js.map +2 -2
  20. package/dist/modules/customers/commands/shared.js +2 -1
  21. package/dist/modules/customers/commands/shared.js.map +2 -2
  22. package/dist/modules/customers/components/DictionarySettings.js +5 -0
  23. package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
  24. package/dist/modules/customers/components/DictionarySortSettings.js +5 -0
  25. package/dist/modules/customers/components/DictionarySortSettings.js.map +2 -2
  26. package/dist/modules/customers/components/detail/ActivitiesCard.js +3 -2
  27. package/dist/modules/customers/components/detail/ActivitiesCard.js.map +2 -2
  28. package/dist/modules/customers/components/detail/ActivityCard.js +2 -1
  29. package/dist/modules/customers/components/detail/ActivityCard.js.map +2 -2
  30. package/dist/modules/customers/components/detail/ActivityTimeline.js +3 -2
  31. package/dist/modules/customers/components/detail/ActivityTimeline.js.map +2 -2
  32. package/dist/modules/customers/components/detail/TaskDialog.js +3 -2
  33. package/dist/modules/customers/components/detail/TaskDialog.js.map +2 -2
  34. package/dist/modules/customers/components/detail/TaskForm.js +170 -69
  35. package/dist/modules/customers/components/detail/TaskForm.js.map +2 -2
  36. package/dist/modules/customers/components/detail/TasksSection.js +27 -4
  37. package/dist/modules/customers/components/detail/TasksSection.js.map +2 -2
  38. package/dist/modules/customers/components/detail/hooks/usePersonTasks.js.map +2 -2
  39. package/dist/modules/customers/data/enrichers.js +2 -2
  40. package/dist/modules/customers/data/enrichers.js.map +2 -2
  41. package/dist/modules/customers/data/validators.js +6 -2
  42. package/dist/modules/customers/data/validators.js.map +2 -2
  43. package/dist/modules/customers/lib/dictionaries.js +2 -1
  44. package/dist/modules/customers/lib/dictionaries.js.map +2 -2
  45. package/dist/modules/customers/lib/interactionProjection.js +2 -1
  46. package/dist/modules/customers/lib/interactionProjection.js.map +2 -2
  47. package/dist/modules/customers/lib/interactionStatus.js +24 -0
  48. package/dist/modules/customers/lib/interactionStatus.js.map +7 -0
  49. package/dist/modules/customers/lib/link-channel-message-handler.js +4 -5
  50. package/dist/modules/customers/lib/link-channel-message-handler.js.map +2 -2
  51. package/dist/modules/dictionaries/components/AppearanceSelector.js +7 -3
  52. package/dist/modules/dictionaries/components/AppearanceSelector.js.map +2 -2
  53. package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js +1 -0
  54. package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js.map +2 -2
  55. package/package.json +7 -7
  56. package/src/modules/configs/lib/upgrade-actions.ts +31 -1
  57. package/src/modules/customers/ai-tools/activities-tasks-pack.ts +12 -3
  58. package/src/modules/customers/api/companies/[id]/route.ts +3 -2
  59. package/src/modules/customers/api/dictionaries/context.ts +2 -0
  60. package/src/modules/customers/api/interactions/conflicts/route.ts +2 -1
  61. package/src/modules/customers/api/interactions/counts/route.ts +7 -2
  62. package/src/modules/customers/api/people/[id]/route.ts +3 -2
  63. package/src/modules/customers/cli.ts +19 -0
  64. package/src/modules/customers/commands/interactions.ts +8 -3
  65. package/src/modules/customers/commands/shared.ts +1 -0
  66. package/src/modules/customers/components/DictionarySettings.tsx +5 -0
  67. package/src/modules/customers/components/DictionarySortSettings.tsx +5 -0
  68. package/src/modules/customers/components/detail/ActivitiesCard.tsx +3 -2
  69. package/src/modules/customers/components/detail/ActivityCard.tsx +2 -1
  70. package/src/modules/customers/components/detail/ActivityTimeline.tsx +3 -2
  71. package/src/modules/customers/components/detail/TaskDialog.tsx +4 -1
  72. package/src/modules/customers/components/detail/TaskForm.tsx +218 -71
  73. package/src/modules/customers/components/detail/TasksSection.tsx +28 -2
  74. package/src/modules/customers/components/detail/hooks/usePersonTasks.ts +1 -0
  75. package/src/modules/customers/data/enrichers.ts +2 -7
  76. package/src/modules/customers/data/validators.ts +16 -2
  77. package/src/modules/customers/i18n/de.json +10 -0
  78. package/src/modules/customers/i18n/en.json +10 -0
  79. package/src/modules/customers/i18n/es.json +10 -0
  80. package/src/modules/customers/i18n/pl.json +10 -0
  81. package/src/modules/customers/lib/dictionaries.ts +1 -0
  82. package/src/modules/customers/lib/interactionProjection.ts +6 -2
  83. package/src/modules/customers/lib/interactionStatus.ts +30 -0
  84. package/src/modules/customers/lib/link-channel-message-handler.ts +4 -5
  85. package/src/modules/dictionaries/components/AppearanceSelector.tsx +10 -1
  86. package/src/modules/dictionaries/components/DictionaryEntriesEditor.tsx +1 -0
@@ -836,6 +836,8 @@
836
836
  "customers.config.dictionaries.sections.dealStatuses.title": "Deal statuses",
837
837
  "customers.config.dictionaries.sections.industries.description": "Manage the industries used by companies.",
838
838
  "customers.config.dictionaries.sections.industries.title": "Industries",
839
+ "customers.config.dictionaries.sections.interactionStatuses.description": "Manage the statuses available for tasks and logged interactions.",
840
+ "customers.config.dictionaries.sections.interactionStatuses.title": "Interaction statuses",
839
841
  "customers.config.dictionaries.sections.jobTitles.description": "Configure job titles with their appearance.",
840
842
  "customers.config.dictionaries.sections.jobTitles.title": "Job titles",
841
843
  "customers.config.dictionaries.sections.lifecycle.description": "Configure lifecycle stages to track customer progress.",
@@ -861,6 +863,10 @@
861
863
  "customers.config.nav.customers": "Customers",
862
864
  "customers.config.pipelineStages.deletePipelineDesc": "This pipeline and all its stages will be permanently removed. Deals assigned to it will lose their pipeline assignment.",
863
865
  "customers.config.pipelineStages.deleteStageDesc": "This stage will be permanently removed. Deals assigned to it will lose their stage assignment.",
866
+ "customers.config.upgradeActions.interactionStatuses.cta": "Seed interaction statuses",
867
+ "customers.config.upgradeActions.interactionStatuses.loading": "Seeding interaction statuses…",
868
+ "customers.config.upgradeActions.interactionStatuses.message": "Seed the interaction-statuses dictionary so CRM tasks and logged interactions can use the configurable status list.",
869
+ "customers.config.upgradeActions.interactionStatuses.success": "Interaction statuses are ready.",
864
870
  "customers.countries.ca": "Canada",
865
871
  "customers.countries.de": "Germany",
866
872
  "customers.countries.es": "Spain",
@@ -1609,7 +1615,9 @@
1609
1615
  "customers.interactions.load.error": "Failed to load interactions.",
1610
1616
  "customers.interactions.status.canceled": "Canceled",
1611
1617
  "customers.interactions.status.done": "Done",
1618
+ "customers.interactions.status.in_progress": "In progress",
1612
1619
  "customers.interactions.status.planned": "Planned",
1620
+ "customers.interactions.status.waiting": "Waiting / blocked",
1613
1621
  "customers.interactions.update.error": "Failed to update interaction.",
1614
1622
  "customers.interactions.update.success": "Interaction updated.",
1615
1623
  "customers.linking.actions.cancel": "Cancel",
@@ -2126,6 +2134,8 @@
2126
2134
  "customers.people.detail.tasks.fields.priorityPlaceholder": "0-100",
2127
2135
  "customers.people.detail.tasks.fields.scheduledAt": "Scheduled for",
2128
2136
  "customers.people.detail.tasks.fields.scheduledAtPlaceholder": "Choose a date and time",
2137
+ "customers.people.detail.tasks.fields.status": "Status",
2138
+ "customers.people.detail.tasks.fields.statusPlaceholder": "Select a status",
2129
2139
  "customers.people.detail.tasks.fields.title": "Title",
2130
2140
  "customers.people.detail.tasks.fields.titlePlaceholder": "Task title",
2131
2141
  "customers.people.detail.tasks.form.customFields": "Task attributes",
@@ -836,6 +836,8 @@
836
836
  "customers.config.dictionaries.sections.dealStatuses.title": "Estados de oportunidades",
837
837
  "customers.config.dictionaries.sections.industries.description": "Gestiona las industrias utilizadas por las empresas.",
838
838
  "customers.config.dictionaries.sections.industries.title": "Industrias",
839
+ "customers.config.dictionaries.sections.interactionStatuses.description": "Manage the statuses available for tasks and logged interactions.",
840
+ "customers.config.dictionaries.sections.interactionStatuses.title": "Interaction statuses",
839
841
  "customers.config.dictionaries.sections.jobTitles.description": "Configura los cargos y su apariencia.",
840
842
  "customers.config.dictionaries.sections.jobTitles.title": "Cargos",
841
843
  "customers.config.dictionaries.sections.lifecycle.description": "Configura las etapas del ciclo de vida para seguir el progreso de los clientes.",
@@ -861,6 +863,10 @@
861
863
  "customers.config.nav.customers": "Clientes",
862
864
  "customers.config.pipelineStages.deletePipelineDesc": "This pipeline and all its stages will be permanently removed. Deals assigned to it will lose their pipeline assignment.",
863
865
  "customers.config.pipelineStages.deleteStageDesc": "This stage will be permanently removed. Deals assigned to it will lose their stage assignment.",
866
+ "customers.config.upgradeActions.interactionStatuses.cta": "Seed interaction statuses",
867
+ "customers.config.upgradeActions.interactionStatuses.loading": "Seeding interaction statuses…",
868
+ "customers.config.upgradeActions.interactionStatuses.message": "Seed the interaction-statuses dictionary so CRM tasks and logged interactions can use the configurable status list.",
869
+ "customers.config.upgradeActions.interactionStatuses.success": "Interaction statuses are ready.",
864
870
  "customers.countries.ca": "Canadá",
865
871
  "customers.countries.de": "Alemania",
866
872
  "customers.countries.es": "España",
@@ -1609,7 +1615,9 @@
1609
1615
  "customers.interactions.load.error": "Failed to load interactions.",
1610
1616
  "customers.interactions.status.canceled": "Canceled",
1611
1617
  "customers.interactions.status.done": "Done",
1618
+ "customers.interactions.status.in_progress": "In progress",
1612
1619
  "customers.interactions.status.planned": "Planned",
1620
+ "customers.interactions.status.waiting": "Waiting / blocked",
1613
1621
  "customers.interactions.update.error": "Failed to update interaction.",
1614
1622
  "customers.interactions.update.success": "Interaction updated.",
1615
1623
  "customers.linking.actions.cancel": "Cancelar",
@@ -2126,6 +2134,8 @@
2126
2134
  "customers.people.detail.tasks.fields.priorityPlaceholder": "0-100",
2127
2135
  "customers.people.detail.tasks.fields.scheduledAt": "Programada para",
2128
2136
  "customers.people.detail.tasks.fields.scheduledAtPlaceholder": "Elige una fecha y hora",
2137
+ "customers.people.detail.tasks.fields.status": "Status",
2138
+ "customers.people.detail.tasks.fields.statusPlaceholder": "Selecciona un estado",
2129
2139
  "customers.people.detail.tasks.fields.title": "Título",
2130
2140
  "customers.people.detail.tasks.fields.titlePlaceholder": "Título de la tarea",
2131
2141
  "customers.people.detail.tasks.form.customFields": "Atributos de tarea",
@@ -836,6 +836,8 @@
836
836
  "customers.config.dictionaries.sections.dealStatuses.title": "Statusy szans sprzedaży",
837
837
  "customers.config.dictionaries.sections.industries.description": "Zarządzaj branżami używanymi przez firmy.",
838
838
  "customers.config.dictionaries.sections.industries.title": "Branże",
839
+ "customers.config.dictionaries.sections.interactionStatuses.description": "Manage the statuses available for tasks and logged interactions.",
840
+ "customers.config.dictionaries.sections.interactionStatuses.title": "Interaction statuses",
839
841
  "customers.config.dictionaries.sections.jobTitles.description": "Skonfiguruj stanowiska i ich wygląd.",
840
842
  "customers.config.dictionaries.sections.jobTitles.title": "Stanowiska",
841
843
  "customers.config.dictionaries.sections.lifecycle.description": "Skonfiguruj etapy cyklu życia do śledzenia postępu klientów.",
@@ -861,6 +863,10 @@
861
863
  "customers.config.nav.customers": "Klienci",
862
864
  "customers.config.pipelineStages.deletePipelineDesc": "This pipeline and all its stages will be permanently removed. Deals assigned to it will lose their pipeline assignment.",
863
865
  "customers.config.pipelineStages.deleteStageDesc": "This stage will be permanently removed. Deals assigned to it will lose their stage assignment.",
866
+ "customers.config.upgradeActions.interactionStatuses.cta": "Seed interaction statuses",
867
+ "customers.config.upgradeActions.interactionStatuses.loading": "Seeding interaction statuses…",
868
+ "customers.config.upgradeActions.interactionStatuses.message": "Seed the interaction-statuses dictionary so CRM tasks and logged interactions can use the configurable status list.",
869
+ "customers.config.upgradeActions.interactionStatuses.success": "Interaction statuses are ready.",
864
870
  "customers.countries.ca": "Kanada",
865
871
  "customers.countries.de": "Niemcy",
866
872
  "customers.countries.es": "Hiszpania",
@@ -1609,7 +1615,9 @@
1609
1615
  "customers.interactions.load.error": "Failed to load interactions.",
1610
1616
  "customers.interactions.status.canceled": "Canceled",
1611
1617
  "customers.interactions.status.done": "Done",
1618
+ "customers.interactions.status.in_progress": "In progress",
1612
1619
  "customers.interactions.status.planned": "Planned",
1620
+ "customers.interactions.status.waiting": "Waiting / blocked",
1613
1621
  "customers.interactions.update.error": "Failed to update interaction.",
1614
1622
  "customers.interactions.update.success": "Interaction updated.",
1615
1623
  "customers.linking.actions.cancel": "Anuluj",
@@ -2126,6 +2134,8 @@
2126
2134
  "customers.people.detail.tasks.fields.priorityPlaceholder": "0-100",
2127
2135
  "customers.people.detail.tasks.fields.scheduledAt": "Zaplanowano na",
2128
2136
  "customers.people.detail.tasks.fields.scheduledAtPlaceholder": "Wybierz datę i godzinę",
2137
+ "customers.people.detail.tasks.fields.status": "Status",
2138
+ "customers.people.detail.tasks.fields.statusPlaceholder": "Wybierz status",
2129
2139
  "customers.people.detail.tasks.fields.title": "Tytuł",
2130
2140
  "customers.people.detail.tasks.fields.titlePlaceholder": "Tytuł zadania",
2131
2141
  "customers.people.detail.tasks.form.customFields": "Atrybuty zadania",
@@ -24,6 +24,7 @@ export const CUSTOMER_DICTIONARY_KINDS = [
24
24
  'temperature',
25
25
  'renewal-quarters',
26
26
  'person-company-roles',
27
+ 'interaction-statuses',
27
28
  ] as const
28
29
 
29
30
  export type CustomerDictionaryKind = typeof CUSTOMER_DICTIONARY_KINDS[number]
@@ -1,5 +1,6 @@
1
1
  import type { EntityManager } from '@mikro-orm/postgresql'
2
2
  import { sql } from 'kysely'
3
+ import { TERMINAL_INTERACTION_STATUS_LIST } from './interactionStatus'
3
4
 
4
5
  export type NextInteractionProjectionResult = {
5
6
  nextInteractionId: string | null
@@ -9,7 +10,10 @@ export type NextInteractionProjectionResult = {
9
10
  * Recomputes the next-interaction projection fields on a CustomerEntity.
10
11
  *
11
12
  * Algorithm:
12
- * 1. Find all interactions for entityId where status = 'planned', scheduled_at IS NOT NULL, deleted_at IS NULL
13
+ * 1. Find all OPEN interactions for entityId (status NOT IN terminal — see lib/interactionStatus.ts),
14
+ * scheduled_at IS NOT NULL, deleted_at IS NULL. A scheduled task that moves planned -> in_progress
15
+ * stays the next step; this projection only tracks scheduled next-steps, so undated open tasks are
16
+ * deliberately excluded (next_interaction_at must stay a real date for grid sort / search index).
13
17
  * 2. Sort by scheduled_at ASC, priority DESC NULLS LAST, created_at ASC, id ASC
14
18
  * 3. Take the first row and project its fields onto the CustomerEntity
15
19
  * 4. If no candidates, set all projection fields to NULL
@@ -32,7 +36,7 @@ export async function recomputeNextInteraction(
32
36
  'appearance_color',
33
37
  ])
34
38
  .where('entity_id', '=', entityId)
35
- .where('status', '=', 'planned')
39
+ .where('status', 'not in', [...TERMINAL_INTERACTION_STATUS_LIST])
36
40
  .where('scheduled_at', 'is not', null)
37
41
  .where('deleted_at', 'is', null)
38
42
  if (organizationId) {
@@ -0,0 +1,30 @@
1
+ // Single source of truth for interaction (CRM task) open/terminal semantics.
2
+ //
3
+ // The editable list of statuses lives in the `interaction-statuses` dictionary and
4
+ // is configurable per tenant. The behaviorally-significant question — does a status
5
+ // mean the task is still open, or is it closed/terminal — stays in code here so every
6
+ // call site agrees. A status not known to code counts as OPEN (non-terminal): it shows
7
+ // in the open-activities badge and is never auto-treated as completed. That is the safe
8
+ // default and matches the deal open-activities enricher's `status NOT IN (terminal)` filter.
9
+
10
+ export const INTERACTION_STATUS_COMPLETED = 'done' as const
11
+ export const INTERACTION_STATUS_CANCELED = 'canceled' as const
12
+ export const INTERACTION_STATUS_PLANNED = 'planned' as const
13
+
14
+ // `completed` is a legacy spelling accepted defensively; `done` is the canonical
15
+ // terminal-success value persisted by the complete action and the email logger.
16
+ export const TERMINAL_INTERACTION_STATUS_LIST: readonly string[] = [
17
+ INTERACTION_STATUS_COMPLETED,
18
+ INTERACTION_STATUS_CANCELED,
19
+ 'completed',
20
+ ]
21
+
22
+ const TERMINAL_INTERACTION_STATUSES = new Set<string>(TERMINAL_INTERACTION_STATUS_LIST)
23
+
24
+ export function isTerminalInteractionStatus(value: string | null | undefined): boolean {
25
+ return value != null && TERMINAL_INTERACTION_STATUSES.has(value)
26
+ }
27
+
28
+ export function isOpenInteractionStatus(value: string | null | undefined): boolean {
29
+ return !isTerminalInteractionStatus(value)
30
+ }
@@ -3,6 +3,7 @@ import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/
3
3
  import { CustomerEntity, CustomerInteraction } from '../data/entities'
4
4
  import { findPeopleByAddresses, normalizeAddresses } from './findPeopleByAddresses'
5
5
  import { emitCustomersEvent } from '../events'
6
+ import { INTERACTION_STATUS_COMPLETED } from './interactionStatus'
6
7
 
7
8
  /**
8
9
  * Shared implementation for the link-channel-message subscribers.
@@ -481,11 +482,9 @@ async function persistInteractions(
481
482
  // scheduled work. Without an explicit status the entity default
482
483
  // ('planned') combined with a past `occurredAt` makes the activity
483
484
  // timeline render the email as "overdue", which is the wrong UX.
484
- // The canonical "completed" value is `'done'` per
485
- // `validators.ts:interactionStatusValues = ['planned', 'done', 'canceled']`
486
- // — `'completed'` is a legacy spelling that the enricher accepts
487
- // defensively but the activity timeline `isOverdue` predicate does NOT.
488
- status: 'done',
485
+ // The canonical terminal-success value is `INTERACTION_STATUS_COMPLETED`
486
+ // ('done'); see `lib/interactionStatus.ts` for the open/terminal semantics.
487
+ status: INTERACTION_STATUS_COMPLETED,
489
488
  externalMessageId: data.linkId,
490
489
  visibility: data.visibility,
491
490
  channelProviderKey: data.channelProviderKey,
@@ -31,6 +31,11 @@ type AppearanceSelectorProps = {
31
31
  iconSuggestions?: IconOption[]
32
32
  iconLibrary?: IconOption[]
33
33
  className?: string
34
+ /**
35
+ * Title/value shown alongside the icon and color swatch in the preview, so the entry
36
+ * previews the way it renders elsewhere (icon + color + label). Omit to preview appearance only.
37
+ */
38
+ previewLabel?: string | null
34
39
  }
35
40
 
36
41
  const ICON_PICKER_LIMIT = 240
@@ -45,10 +50,13 @@ export function AppearanceSelector({
45
50
  iconSuggestions = ICON_SUGGESTIONS,
46
51
  iconLibrary,
47
52
  className,
53
+ previewLabel,
48
54
  }: AppearanceSelectorProps) {
49
55
  const normalizedIcon = icon ?? ''
50
56
  const normalizedColor = color ?? '#000000'
51
57
  const hasAppearance = Boolean(icon) || Boolean(color)
58
+ const trimmedPreviewLabel = typeof previewLabel === 'string' ? previewLabel.trim() : ''
59
+ const hasPreviewContent = hasAppearance || trimmedPreviewLabel.length > 0
52
60
  const iconOptions = React.useMemo(() => (iconLibrary && iconLibrary.length ? iconLibrary : ICON_LIBRARY), [iconLibrary])
53
61
  const [pickerOpen, setPickerOpen] = React.useState(false)
54
62
  const [iconSearch, setIconSearch] = React.useState('')
@@ -240,10 +248,11 @@ export function AppearanceSelector({
240
248
  <div>
241
249
  <label className="text-sm font-medium">Preview</label>
242
250
  <div className="flex items-center gap-3 rounded border border-dashed border-border px-3 py-2">
243
- {hasAppearance ? (
251
+ {hasPreviewContent ? (
244
252
  <>
245
253
  {renderDictionaryIcon(icon, 'h-5 w-5')}
246
254
  {renderDictionaryColor(color, 'h-4 w-4 rounded-full')}
255
+ {trimmedPreviewLabel ? <span className="text-sm">{trimmedPreviewLabel}</span> : null}
247
256
  </>
248
257
  ) : (
249
258
  <span className="text-sm text-muted-foreground">{labels.previewEmptyLabel}</span>
@@ -471,6 +471,7 @@ export function DictionaryEntriesEditor({ dictionaryId, dictionaryName, readOnly
471
471
  <AppearanceSelector
472
472
  icon={appearance.icon}
473
473
  color={appearance.color}
474
+ previewLabel={formState.label.trim() || formState.value.trim()}
474
475
  onIconChange={(next) => {
475
476
  appearance.setIcon(next)
476
477
  setFormState((prev) => ({ ...prev, icon: next }))