@open-mercato/core 0.6.1-develop.3256.1.fe3dec2464 → 0.6.1-develop.3276.1.1fcd145078
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/.turbo/turbo-build.log +1 -1
- package/dist/helpers/integration/authFixtures.js.map +1 -1
- package/dist/helpers/integration/authUi.js +10 -2
- package/dist/helpers/integration/authUi.js.map +2 -2
- package/dist/helpers/integration/salesUi.js +3 -1
- package/dist/helpers/integration/salesUi.js.map +2 -2
- package/dist/modules/api_docs/frontend/docs/api/Explorer.js +10 -1
- package/dist/modules/api_docs/frontend/docs/api/Explorer.js.map +2 -2
- package/dist/modules/audit_logs/lib/display-helpers.js +28 -27
- package/dist/modules/audit_logs/lib/display-helpers.js.map +2 -2
- package/dist/modules/auth/api/users/route.js +21 -3
- package/dist/modules/auth/api/users/route.js.map +2 -2
- package/dist/modules/auth/backend/users/[id]/edit/page.js +6 -1
- package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
- package/dist/modules/auth/backend/users/create/page.js +4 -1
- package/dist/modules/auth/backend/users/create/page.js.map +2 -2
- package/dist/modules/auth/backend/users/page.js +56 -2
- package/dist/modules/auth/backend/users/page.js.map +3 -3
- package/dist/modules/auth/commands/users.js +14 -0
- package/dist/modules/auth/commands/users.js.map +2 -2
- package/dist/modules/auth/data/validators.js +8 -0
- package/dist/modules/auth/data/validators.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +1 -1
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +15 -9
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js +26 -3
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +1 -1
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/components/PipelineSettings.js +108 -99
- package/dist/modules/customers/components/PipelineSettings.js.map +2 -2
- package/dist/modules/customers/components/detail/AssignRoleDialog.js +13 -5
- package/dist/modules/customers/components/detail/AssignRoleDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/EntityTagsDialog.js +10 -2
- package/dist/modules/customers/components/detail/EntityTagsDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/PersonCompaniesSection.js +15 -8
- package/dist/modules/customers/components/detail/PersonCompaniesSection.js.map +2 -2
- package/dist/modules/customers/components/detail/RolesSection.js +35 -22
- package/dist/modules/customers/components/detail/RolesSection.js.map +2 -2
- package/dist/modules/customers/components/detail/TasksSection.js +2 -2
- package/dist/modules/customers/components/detail/TasksSection.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/DateTimeFields.js +73 -64
- package/dist/modules/customers/components/detail/schedule/DateTimeFields.js.map +2 -2
- package/dist/modules/customers/components/linking/LinkEntityDialog.js +11 -2
- package/dist/modules/customers/components/linking/LinkEntityDialog.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.js +2 -9
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js +14 -29
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js.map +2 -2
- package/dist/modules/dictionaries/components/DictionariesManager.js +10 -2
- package/dist/modules/dictionaries/components/DictionariesManager.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/[id]/page.js +79 -95
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
- package/dist/modules/payment_gateways/backend/payment-gateways/page.js +49 -70
- package/dist/modules/payment_gateways/backend/payment-gateways/page.js.map +2 -2
- package/dist/modules/planner/components/AvailabilityRuleSetForm.js +1 -1
- package/dist/modules/planner/components/AvailabilityRuleSetForm.js.map +1 -1
- package/dist/modules/planner/components/AvailabilityRulesEditor.js +19 -19
- package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
- package/dist/modules/resources/components/ResourceCrudForm.js +1 -1
- package/dist/modules/resources/components/ResourceCrudForm.js.map +1 -1
- package/dist/modules/resources/components/ResourceTypeCrudForm.js +1 -1
- package/dist/modules/resources/components/ResourceTypeCrudForm.js.map +2 -2
- package/dist/modules/sales/components/documents/ReturnDialog.js +11 -8
- package/dist/modules/sales/components/documents/ReturnDialog.js.map +2 -2
- package/dist/modules/staff/backend/staff/profile/create/page.js +1 -1
- package/dist/modules/staff/backend/staff/profile/create/page.js.map +2 -2
- package/dist/modules/staff/components/TeamForm.js +1 -1
- package/dist/modules/staff/components/TeamForm.js.map +2 -2
- package/dist/modules/staff/components/TeamMemberForm.js +1 -1
- package/dist/modules/staff/components/TeamMemberForm.js.map +1 -1
- package/dist/modules/staff/components/TeamRoleForm.js +1 -1
- package/dist/modules/staff/components/TeamRoleForm.js.map +2 -2
- package/package.json +5 -5
- package/src/helpers/integration/authFixtures.ts +1 -1
- package/src/helpers/integration/authUi.ts +11 -2
- package/src/helpers/integration/salesUi.ts +10 -1
- package/src/modules/api_docs/frontend/docs/api/Explorer.tsx +7 -3
- package/src/modules/audit_logs/lib/display-helpers.tsx +22 -14
- package/src/modules/auth/api/users/route.ts +22 -3
- package/src/modules/auth/backend/users/[id]/edit/page.tsx +9 -1
- package/src/modules/auth/backend/users/create/page.tsx +6 -2
- package/src/modules/auth/backend/users/page.tsx +60 -3
- package/src/modules/auth/commands/users.ts +15 -0
- package/src/modules/auth/data/validators.ts +8 -0
- package/src/modules/auth/i18n/de.json +4 -0
- package/src/modules/auth/i18n/en.json +4 -0
- package/src/modules/auth/i18n/es.json +4 -0
- package/src/modules/auth/i18n/pl.json +4 -0
- package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +1 -1
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +12 -13
- package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +17 -13
- package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +1 -1
- package/src/modules/customers/components/PipelineSettings.tsx +95 -95
- package/src/modules/customers/components/detail/AssignRoleDialog.tsx +7 -4
- package/src/modules/customers/components/detail/EntityTagsDialog.tsx +13 -8
- package/src/modules/customers/components/detail/PersonCompaniesSection.tsx +16 -12
- package/src/modules/customers/components/detail/RolesSection.tsx +34 -31
- package/src/modules/customers/components/detail/TasksSection.tsx +16 -8
- package/src/modules/customers/components/detail/schedule/DateTimeFields.tsx +77 -51
- package/src/modules/customers/components/linking/LinkEntityDialog.tsx +7 -3
- package/src/modules/data_sync/backend/data-sync/page.tsx +4 -7
- package/src/modules/data_sync/backend/data-sync/runs/[id]/page.tsx +19 -54
- package/src/modules/dictionaries/components/DictionariesManager.tsx +7 -4
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +101 -146
- package/src/modules/payment_gateways/backend/payment-gateways/page.tsx +60 -103
- package/src/modules/planner/components/AvailabilityRuleSetForm.tsx +1 -1
- package/src/modules/planner/components/AvailabilityRulesEditor.tsx +17 -17
- package/src/modules/resources/components/ResourceCrudForm.tsx +1 -1
- package/src/modules/resources/components/ResourceTypeCrudForm.tsx +1 -1
- package/src/modules/sales/components/documents/ReturnDialog.tsx +10 -7
- package/src/modules/sales/i18n/de.json +2 -0
- package/src/modules/sales/i18n/en.json +2 -0
- package/src/modules/sales/i18n/es.json +2 -0
- package/src/modules/sales/i18n/pl.json +2 -0
- package/src/modules/staff/backend/staff/profile/create/page.tsx +1 -1
- package/src/modules/staff/components/TeamForm.tsx +1 -1
- package/src/modules/staff/components/TeamMemberForm.tsx +1 -1
- package/src/modules/staff/components/TeamRoleForm.tsx +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/staff/components/TeamRoleForm.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { normalizeCustomFieldValues } from '@open-mercato/shared/lib/custom-fields/normalize'\nimport { AppearanceSelector } from '@open-mercato/core/modules/dictionaries/components/AppearanceSelector'\nimport { E } from '#generated/entities.ids.generated'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\nexport type TeamRoleFormValues = {\n id?: string\n teamId?: string | null\n name: string\n description?: string | null\n appearance?: { icon?: string | null; color?: string | null }\n} & Record<string, unknown>\n\nexport type TeamRoleOption = {\n id: string\n name: string\n}\n\nexport type TeamRoleFormProps = {\n title: string\n submitLabel?: string\n backHref: string\n cancelHref: string\n initialValues: TeamRoleFormValues\n teamOptions?: TeamRoleOption[]\n onSubmit: (values: TeamRoleFormValues) => Promise<void>\n onDelete?: () => Promise<void>\n isLoading?: boolean\n loadingMessage?: string\n extraActions?: React.ReactNode\n}\n\nconst normalizeCustomFieldSubmitValue = (value: unknown): unknown => {\n const normalized = normalizeCustomFieldValues({ value })\n return normalized.value\n}\n\nexport const buildTeamRolePayload = (\n values: TeamRoleFormValues,\n options: { id?: string } = {},\n): Record<string, unknown> => {\n const name = typeof values.name === 'string' ? values.name.trim() : ''\n const description = typeof values.description === 'string' && values.description.trim().length\n ? values.description.trim()\n : null\n const teamId = typeof values.teamId === 'string' && values.teamId.trim().length ? values.teamId : null\n const appearance = values.appearance && typeof values.appearance === 'object'\n ? values.appearance as { icon?: string | null; color?: string | null }\n : {}\n const customFields = collectCustomFieldValues(values, { transform: normalizeCustomFieldSubmitValue })\n return {\n ...(options.id ? { id: options.id } : {}),\n teamId,\n name,\n description,\n appearanceIcon: appearance.icon ?? null,\n appearanceColor: appearance.color ?? null,\n ...(Object.keys(customFields).length ? { customFields } : {}),\n }\n}\n\nexport function TeamRoleForm(props: TeamRoleFormProps) {\n const {\n title,\n submitLabel,\n backHref,\n cancelHref,\n initialValues,\n teamOptions,\n onSubmit,\n onDelete,\n isLoading,\n loadingMessage,\n extraActions,\n } = props\n const t = useT()\n\n const appearanceLabels = React.useMemo(() => ({\n colorLabel: t('staff.teamRoles.form.appearance.colorLabel', 'Color'),\n colorHelp: t('staff.teamRoles.form.appearance.colorHelp', 'Pick a color for this team role.'),\n colorClearLabel: t('staff.teamRoles.form.appearance.colorClear', 'Clear color'),\n iconLabel: t('staff.teamRoles.form.appearance.iconLabel', 'Icon'),\n iconPlaceholder: t('staff.teamRoles.form.appearance.iconPlaceholder', 'Type an emoji or icon name'),\n iconPickerTriggerLabel: t('staff.teamRoles.form.appearance.iconPicker', 'Browse icons'),\n iconSearchPlaceholder: t('staff.teamRoles.form.appearance.iconSearch', 'Search icons or emojis\u2026'),\n iconSearchEmptyLabel: t('staff.teamRoles.form.appearance.iconSearchEmpty', 'No icons match your search'),\n iconSuggestionsLabel: t('staff.teamRoles.form.appearance.iconSuggestions', 'Suggestions'),\n iconClearLabel: t('staff.teamRoles.form.appearance.iconClear', 'Clear icon'),\n previewEmptyLabel: t('staff.teamRoles.form.appearance.previewEmpty', 'No appearance selected'),\n }), [t])\n\n const fields = React.useMemo<CrudField[]>(() => {\n const base: CrudField[] = []\n if (teamOptions && teamOptions.length) {\n base.push({\n id: 'teamId',\n label: t('staff.teamRoles.form.fields.team', 'Team'),\n type: 'select',\n listbox: true,\n options: [\n { value: '', label: t('staff.teamRoles.form.fields.team.unassigned', 'Unassigned') },\n ...teamOptions.map((team) => ({ value: team.id, label: team.name })),\n ],\n })\n }\n base.push(\n { id: 'name', label: t('staff.teamRoles.form.fields.name', 'Name'), type: 'text', required: true },\n { id: 'description', label: t('staff.teamRoles.form.fields.description', 'Description'), type: 'richtext' },\n {\n id: 'appearance',\n label: t('staff.teamRoles.form.appearance.label', 'Appearance'),\n type: 'custom',\n component: ({ value, setValue }) => {\n const current = value && typeof value === 'object'\n ? (value as { icon?: string | null; color?: string | null })\n : {}\n return (\n <AppearanceSelector\n icon={current.icon ?? null}\n color={current.color ?? null}\n onIconChange={(next) => setValue({ ...current, icon: next })}\n onColorChange={(next) => setValue({ ...current, color: next })}\n labels={appearanceLabels}\n />\n )\n },\n },\n )\n return base\n }, [appearanceLabels, t, teamOptions])\n\n const groups = React.useMemo<CrudFormGroup[]>(() => [\n { id: 'details', fields: teamOptions && teamOptions.length ? ['teamId', 'name', 'description', 'appearance'] : ['name', 'description', 'appearance'] },\n { id: 'custom', title: t('entities.customFields.title', 'Custom Attributes'), column: 2, kind: 'customFields' },\n ], [t, teamOptions])\n\n return (\n <CrudForm<TeamRoleFormValues>\n title={title}\n backHref={backHref}\n cancelHref={cancelHref}\n versionHistory={initialValues.id\n ? { resourceKind: 'staff.teamRole', resourceId: String(initialValues.id) }\n : undefined}\n submitLabel={submitLabel}\n fields={fields}\n groups={groups}\n entityId={E.staff.staff_team_role}\n initialValues={initialValues}\n onSubmit={onSubmit}\n onDelete={onDelete}\n isLoading={isLoading}\n loadingMessage={loadingMessage}\n extraActions={extraActions}\n />\n )\n}\n"],
|
|
5
|
-
"mappings": ";AA0HY;AAxHZ,YAAY,WAAW;AACvB,SAAS,gBAAoD;AAC7D,SAAS,gCAAgC;AACzC,SAAS,kCAAkC;AAC3C,SAAS,0BAA0B;AACnC,SAAS,SAAS;AAClB,SAAS,YAAY;AA6BrB,MAAM,kCAAkC,CAAC,UAA4B;AACnE,QAAM,aAAa,2BAA2B,EAAE,MAAM,CAAC;AACvD,SAAO,WAAW;AACpB;AAEO,MAAM,uBAAuB,CAClC,QACA,UAA2B,CAAC,MACA;AAC5B,QAAM,OAAO,OAAO,OAAO,SAAS,WAAW,OAAO,KAAK,KAAK,IAAI;AACpE,QAAM,cAAc,OAAO,OAAO,gBAAgB,YAAY,OAAO,YAAY,KAAK,EAAE,SACpF,OAAO,YAAY,KAAK,IACxB;AACJ,QAAM,SAAS,OAAO,OAAO,WAAW,YAAY,OAAO,OAAO,KAAK,EAAE,SAAS,OAAO,SAAS;AAClG,QAAM,aAAa,OAAO,cAAc,OAAO,OAAO,eAAe,WACjE,OAAO,aACP,CAAC;AACL,QAAM,eAAe,yBAAyB,QAAQ,EAAE,WAAW,gCAAgC,CAAC;AACpG,SAAO;AAAA,IACL,GAAI,QAAQ,KAAK,EAAE,IAAI,QAAQ,GAAG,IAAI,CAAC;AAAA,IACvC;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB,WAAW,QAAQ;AAAA,IACnC,iBAAiB,WAAW,SAAS;AAAA,IACrC,GAAI,OAAO,KAAK,YAAY,EAAE,SAAS,EAAE,aAAa,IAAI,CAAC;AAAA,EAC7D;AACF;AAEO,SAAS,aAAa,OAA0B;AACrD,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AACJ,QAAM,IAAI,KAAK;AAEf,QAAM,mBAAmB,MAAM,QAAQ,OAAO;AAAA,IAC5C,YAAY,EAAE,8CAA8C,OAAO;AAAA,IACnE,WAAW,EAAE,6CAA6C,kCAAkC;AAAA,IAC5F,iBAAiB,EAAE,8CAA8C,aAAa;AAAA,IAC9E,WAAW,EAAE,6CAA6C,MAAM;AAAA,IAChE,iBAAiB,EAAE,mDAAmD,4BAA4B;AAAA,IAClG,wBAAwB,EAAE,8CAA8C,cAAc;AAAA,IACtF,uBAAuB,EAAE,8CAA8C,8BAAyB;AAAA,IAChG,sBAAsB,EAAE,mDAAmD,4BAA4B;AAAA,IACvG,sBAAsB,EAAE,mDAAmD,aAAa;AAAA,IACxF,gBAAgB,EAAE,6CAA6C,YAAY;AAAA,IAC3E,mBAAmB,EAAE,gDAAgD,wBAAwB;AAAA,EAC/F,IAAI,CAAC,CAAC,CAAC;AAEP,QAAM,SAAS,MAAM,QAAqB,MAAM;AAC9C,UAAM,OAAoB,CAAC;AAC3B,QAAI,eAAe,YAAY,QAAQ;AACrC,WAAK,KAAK;AAAA,QACR,IAAI;AAAA,QACJ,OAAO,EAAE,oCAAoC,MAAM;AAAA,QACnD,MAAM;AAAA,QACN,SAAS;AAAA,QACT,SAAS;AAAA,UACP,EAAE,OAAO,IAAI,OAAO,EAAE,+CAA+C,YAAY,EAAE;AAAA,UACnF,GAAG,YAAY,IAAI,CAAC,UAAU,EAAE,OAAO,KAAK,IAAI,OAAO,KAAK,KAAK,EAAE;AAAA,QACrE;AAAA,MACF,CAAC;AAAA,IACH;AACA,SAAK;AAAA,MACH,EAAE,IAAI,QAAQ,OAAO,EAAE,oCAAoC,MAAM,GAAG,MAAM,QAAQ,UAAU,KAAK;AAAA,MACjG,EAAE,IAAI,eAAe,OAAO,EAAE,2CAA2C,aAAa,GAAG,MAAM,
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { normalizeCustomFieldValues } from '@open-mercato/shared/lib/custom-fields/normalize'\nimport { AppearanceSelector } from '@open-mercato/core/modules/dictionaries/components/AppearanceSelector'\nimport { E } from '#generated/entities.ids.generated'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\nexport type TeamRoleFormValues = {\n id?: string\n teamId?: string | null\n name: string\n description?: string | null\n appearance?: { icon?: string | null; color?: string | null }\n} & Record<string, unknown>\n\nexport type TeamRoleOption = {\n id: string\n name: string\n}\n\nexport type TeamRoleFormProps = {\n title: string\n submitLabel?: string\n backHref: string\n cancelHref: string\n initialValues: TeamRoleFormValues\n teamOptions?: TeamRoleOption[]\n onSubmit: (values: TeamRoleFormValues) => Promise<void>\n onDelete?: () => Promise<void>\n isLoading?: boolean\n loadingMessage?: string\n extraActions?: React.ReactNode\n}\n\nconst normalizeCustomFieldSubmitValue = (value: unknown): unknown => {\n const normalized = normalizeCustomFieldValues({ value })\n return normalized.value\n}\n\nexport const buildTeamRolePayload = (\n values: TeamRoleFormValues,\n options: { id?: string } = {},\n): Record<string, unknown> => {\n const name = typeof values.name === 'string' ? values.name.trim() : ''\n const description = typeof values.description === 'string' && values.description.trim().length\n ? values.description.trim()\n : null\n const teamId = typeof values.teamId === 'string' && values.teamId.trim().length ? values.teamId : null\n const appearance = values.appearance && typeof values.appearance === 'object'\n ? values.appearance as { icon?: string | null; color?: string | null }\n : {}\n const customFields = collectCustomFieldValues(values, { transform: normalizeCustomFieldSubmitValue })\n return {\n ...(options.id ? { id: options.id } : {}),\n teamId,\n name,\n description,\n appearanceIcon: appearance.icon ?? null,\n appearanceColor: appearance.color ?? null,\n ...(Object.keys(customFields).length ? { customFields } : {}),\n }\n}\n\nexport function TeamRoleForm(props: TeamRoleFormProps) {\n const {\n title,\n submitLabel,\n backHref,\n cancelHref,\n initialValues,\n teamOptions,\n onSubmit,\n onDelete,\n isLoading,\n loadingMessage,\n extraActions,\n } = props\n const t = useT()\n\n const appearanceLabels = React.useMemo(() => ({\n colorLabel: t('staff.teamRoles.form.appearance.colorLabel', 'Color'),\n colorHelp: t('staff.teamRoles.form.appearance.colorHelp', 'Pick a color for this team role.'),\n colorClearLabel: t('staff.teamRoles.form.appearance.colorClear', 'Clear color'),\n iconLabel: t('staff.teamRoles.form.appearance.iconLabel', 'Icon'),\n iconPlaceholder: t('staff.teamRoles.form.appearance.iconPlaceholder', 'Type an emoji or icon name'),\n iconPickerTriggerLabel: t('staff.teamRoles.form.appearance.iconPicker', 'Browse icons'),\n iconSearchPlaceholder: t('staff.teamRoles.form.appearance.iconSearch', 'Search icons or emojis\u2026'),\n iconSearchEmptyLabel: t('staff.teamRoles.form.appearance.iconSearchEmpty', 'No icons match your search'),\n iconSuggestionsLabel: t('staff.teamRoles.form.appearance.iconSuggestions', 'Suggestions'),\n iconClearLabel: t('staff.teamRoles.form.appearance.iconClear', 'Clear icon'),\n previewEmptyLabel: t('staff.teamRoles.form.appearance.previewEmpty', 'No appearance selected'),\n }), [t])\n\n const fields = React.useMemo<CrudField[]>(() => {\n const base: CrudField[] = []\n if (teamOptions && teamOptions.length) {\n base.push({\n id: 'teamId',\n label: t('staff.teamRoles.form.fields.team', 'Team'),\n type: 'select',\n listbox: true,\n options: [\n { value: '', label: t('staff.teamRoles.form.fields.team.unassigned', 'Unassigned') },\n ...teamOptions.map((team) => ({ value: team.id, label: team.name })),\n ],\n })\n }\n base.push(\n { id: 'name', label: t('staff.teamRoles.form.fields.name', 'Name'), type: 'text', required: true },\n { id: 'description', label: t('staff.teamRoles.form.fields.description', 'Description'), type: 'richtext', editor: 'html' },\n {\n id: 'appearance',\n label: t('staff.teamRoles.form.appearance.label', 'Appearance'),\n type: 'custom',\n component: ({ value, setValue }) => {\n const current = value && typeof value === 'object'\n ? (value as { icon?: string | null; color?: string | null })\n : {}\n return (\n <AppearanceSelector\n icon={current.icon ?? null}\n color={current.color ?? null}\n onIconChange={(next) => setValue({ ...current, icon: next })}\n onColorChange={(next) => setValue({ ...current, color: next })}\n labels={appearanceLabels}\n />\n )\n },\n },\n )\n return base\n }, [appearanceLabels, t, teamOptions])\n\n const groups = React.useMemo<CrudFormGroup[]>(() => [\n { id: 'details', fields: teamOptions && teamOptions.length ? ['teamId', 'name', 'description', 'appearance'] : ['name', 'description', 'appearance'] },\n { id: 'custom', title: t('entities.customFields.title', 'Custom Attributes'), column: 2, kind: 'customFields' },\n ], [t, teamOptions])\n\n return (\n <CrudForm<TeamRoleFormValues>\n title={title}\n backHref={backHref}\n cancelHref={cancelHref}\n versionHistory={initialValues.id\n ? { resourceKind: 'staff.teamRole', resourceId: String(initialValues.id) }\n : undefined}\n submitLabel={submitLabel}\n fields={fields}\n groups={groups}\n entityId={E.staff.staff_team_role}\n initialValues={initialValues}\n onSubmit={onSubmit}\n onDelete={onDelete}\n isLoading={isLoading}\n loadingMessage={loadingMessage}\n extraActions={extraActions}\n />\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.6.1-develop.
|
|
3
|
+
"version": "0.6.1-develop.3276.1.1fcd145078",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -237,12 +237,12 @@
|
|
|
237
237
|
"ts-pattern": "^5.0.0"
|
|
238
238
|
},
|
|
239
239
|
"peerDependencies": {
|
|
240
|
-
"@open-mercato/ai-assistant": "0.6.1-develop.
|
|
241
|
-
"@open-mercato/shared": "0.6.1-develop.
|
|
240
|
+
"@open-mercato/ai-assistant": "0.6.1-develop.3276.1.1fcd145078",
|
|
241
|
+
"@open-mercato/shared": "0.6.1-develop.3276.1.1fcd145078"
|
|
242
242
|
},
|
|
243
243
|
"devDependencies": {
|
|
244
|
-
"@open-mercato/ai-assistant": "0.6.1-develop.
|
|
245
|
-
"@open-mercato/shared": "0.6.1-develop.
|
|
244
|
+
"@open-mercato/ai-assistant": "0.6.1-develop.3276.1.1fcd145078",
|
|
245
|
+
"@open-mercato/shared": "0.6.1-develop.3276.1.1fcd145078",
|
|
246
246
|
"@testing-library/dom": "^10.4.1",
|
|
247
247
|
"@testing-library/jest-dom": "^6.9.1",
|
|
248
248
|
"@testing-library/react": "^16.3.1",
|
|
@@ -34,7 +34,7 @@ export async function deleteRoleIfExists(
|
|
|
34
34
|
export async function createUserFixture(
|
|
35
35
|
request: APIRequestContext,
|
|
36
36
|
token: string,
|
|
37
|
-
input: { email: string; password: string; organizationId: string; roles: string[] },
|
|
37
|
+
input: { email: string; password: string; organizationId: string; roles: string[]; name?: string },
|
|
38
38
|
): Promise<string> {
|
|
39
39
|
const response = await apiRequest(request, 'POST', '/api/auth/users', {
|
|
40
40
|
token,
|
|
@@ -6,8 +6,17 @@ export async function createUserViaUi(page: Page, input: { email: string; passwo
|
|
|
6
6
|
await page.goto('/backend/users/create');
|
|
7
7
|
await expect(page.getByText('Create User')).toBeVisible();
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const emailInput = page.locator('[data-crud-field-id="email"] input').first();
|
|
10
|
+
const nameInput = page.locator('[data-crud-field-id="name"] input').first();
|
|
11
|
+
const passwordInput = page.locator('[data-crud-field-id="password"] input').first();
|
|
12
|
+
|
|
13
|
+
await expect(emailInput).toBeVisible();
|
|
14
|
+
await emailInput.fill(input.email);
|
|
15
|
+
if (await nameInput.count()) {
|
|
16
|
+
await nameInput.fill('');
|
|
17
|
+
}
|
|
18
|
+
await expect(passwordInput).toBeVisible();
|
|
19
|
+
await passwordInput.fill(input.password);
|
|
11
20
|
|
|
12
21
|
const orgSelect = page.locator('main').locator('select').first();
|
|
13
22
|
await expect(orgSelect).toBeEnabled();
|
|
@@ -1090,13 +1090,22 @@ export async function addAdjustment(page: Page, options: AddAdjustmentOptions):
|
|
|
1090
1090
|
const adjustmentRow = page.getByRole('row', { name: new RegExp(escapeRegExp(options.label), 'i') });
|
|
1091
1091
|
const fillAdjustmentForm = async (): Promise<void> => {
|
|
1092
1092
|
await dialog.getByText(/Loading adjustments/i).waitFor({ state: 'hidden', timeout: 3_000 }).catch(() => {});
|
|
1093
|
+
// AdjustmentDialog has a useEffect([initialAdjustment, kindOptions,
|
|
1094
|
+
// loadKindOptions, loadTaxRates, open]) that asynchronously loads tax
|
|
1095
|
+
// rates and resets the form via setInitialValues + a formResetKey bump.
|
|
1096
|
+
// If `.fill()` lands before the reset settles, the label is wiped out.
|
|
1097
|
+
// Wait for the tax-rates network response to land before touching the form.
|
|
1098
|
+
await page.waitForLoadState('networkidle', { timeout: 5_000 }).catch(() => {});
|
|
1093
1099
|
// Radix Select trigger — target by CrudForm field id (kind picker)
|
|
1094
1100
|
const kindTrigger = dialog.locator('[data-crud-field-id="kind"] [role="combobox"]').first();
|
|
1095
1101
|
await expect(kindTrigger).toBeVisible({ timeout: TEST_WAIT_TIMEOUT_MS });
|
|
1096
1102
|
|
|
1097
1103
|
const labelInput = dialog.getByPlaceholder(/e\.g\. Shipping fee/i).first();
|
|
1098
1104
|
await expect(labelInput).toBeVisible({ timeout: TEST_WAIT_TIMEOUT_MS });
|
|
1099
|
-
|
|
1105
|
+
// Bumped toHaveValue timeout 2 s → 5 s — covers the worst-case reset race
|
|
1106
|
+
// where the tax-rates fetch resolves *after* the networkidle window above
|
|
1107
|
+
// (e.g. when a slow Docker host bundles the requests inconsistently).
|
|
1108
|
+
await fillControlledInput(labelInput, options.label, 5_000);
|
|
1100
1109
|
|
|
1101
1110
|
if ((await kindTrigger.count()) > 0) {
|
|
1102
1111
|
const expectedKindValue = normalizeAdjustmentKindValue(options.kindLabel ?? 'Surcharge');
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
import { useEffect, useMemo, useState, type ReactNode } from 'react'
|
|
4
4
|
import Image from 'next/image'
|
|
5
5
|
import Link from 'next/link'
|
|
6
|
+
import { SearchX } from 'lucide-react'
|
|
6
7
|
import { Input } from '@open-mercato/ui/primitives/input'
|
|
8
|
+
import { EmptyState } from '@open-mercato/ui/primitives/empty-state'
|
|
7
9
|
import {
|
|
8
10
|
Select,
|
|
9
11
|
SelectContent,
|
|
@@ -781,9 +783,11 @@ export default function ApiDocsExplorer(props: ApiDocsExplorerProps) {
|
|
|
781
783
|
)
|
|
782
784
|
})}
|
|
783
785
|
{!categories.length ? (
|
|
784
|
-
<
|
|
785
|
-
|
|
786
|
-
|
|
786
|
+
<EmptyState
|
|
787
|
+
icon={<SearchX className="h-8 w-8" aria-hidden="true" />}
|
|
788
|
+
title="No endpoints match your filters."
|
|
789
|
+
description="Try adjusting the method filters or clearing the search query."
|
|
790
|
+
/>
|
|
787
791
|
) : null}
|
|
788
792
|
</div>
|
|
789
793
|
</section>
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import * as React from 'react'
|
|
2
2
|
import type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'
|
|
3
|
+
import {
|
|
4
|
+
Accordion,
|
|
5
|
+
AccordionContent,
|
|
6
|
+
AccordionItem,
|
|
7
|
+
AccordionTrigger,
|
|
8
|
+
} from '@open-mercato/ui/primitives/accordion'
|
|
3
9
|
import type { ChangeRow } from './changeRows'
|
|
4
10
|
import { isRecord } from './changeRows'
|
|
5
11
|
export { extractChangeRows, isRecord } from './changeRows'
|
|
@@ -125,25 +131,27 @@ export type CollapsibleJsonSectionProps = {
|
|
|
125
131
|
|
|
126
132
|
const DEFAULT_TRUNCATE_AT = 5000
|
|
127
133
|
|
|
134
|
+
const COLLAPSIBLE_JSON_ITEM_VALUE = 'open'
|
|
135
|
+
|
|
128
136
|
export function CollapsibleJsonSection({ label, value, truncateAt = DEFAULT_TRUNCATE_AT }: CollapsibleJsonSectionProps) {
|
|
129
|
-
const [
|
|
137
|
+
const [openValue, setOpenValue] = React.useState<string>('')
|
|
130
138
|
const [showFull, setShowFull] = React.useState(false)
|
|
131
139
|
|
|
140
|
+
const isOpen = openValue === COLLAPSIBLE_JSON_ITEM_VALUE
|
|
132
141
|
const stringified = React.useMemo(() => (isOpen ? safeStringify(value) : ''), [isOpen, value])
|
|
133
142
|
const isTruncated = stringified.length > truncateAt
|
|
134
143
|
const displayText = !showFull && isTruncated ? stringified.slice(0, truncateAt) : stringified
|
|
135
144
|
|
|
136
145
|
return (
|
|
137
|
-
<
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
<pre className="mt-2 max-h-96 overflow-auto whitespace-pre-wrap break-words text-xs leading-5 text-muted-foreground">
|
|
146
|
+
<Accordion type="single" collapsible value={openValue} onValueChange={setOpenValue}>
|
|
147
|
+
<AccordionItem value={COLLAPSIBLE_JSON_ITEM_VALUE}>
|
|
148
|
+
<AccordionTrigger>
|
|
149
|
+
<span className="font-semibold text-foreground group-data-[state=open]/accordion-trigger:text-primary">
|
|
150
|
+
{label}
|
|
151
|
+
</span>
|
|
152
|
+
</AccordionTrigger>
|
|
153
|
+
<AccordionContent>
|
|
154
|
+
<pre className="max-h-96 overflow-auto whitespace-pre-wrap break-words text-xs leading-5 text-muted-foreground">
|
|
147
155
|
{displayText}
|
|
148
156
|
{!showFull && isTruncated ? '\n…' : null}
|
|
149
157
|
</pre>
|
|
@@ -156,8 +164,8 @@ export function CollapsibleJsonSection({ label, value, truncateAt = DEFAULT_TRUN
|
|
|
156
164
|
{showFull ? 'Show less' : `Show all (${Math.ceil(stringified.length / 1024)} KB)`}
|
|
157
165
|
</button>
|
|
158
166
|
) : null}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
</
|
|
167
|
+
</AccordionContent>
|
|
168
|
+
</AccordionItem>
|
|
169
|
+
</Accordion>
|
|
162
170
|
)
|
|
163
171
|
}
|
|
@@ -26,6 +26,7 @@ const querySchema = z.object({
|
|
|
26
26
|
page: z.coerce.number().min(1).default(1),
|
|
27
27
|
pageSize: z.coerce.number().min(1).max(100).default(50),
|
|
28
28
|
search: z.string().optional(),
|
|
29
|
+
name: z.string().optional(),
|
|
29
30
|
organizationId: z.string().uuid().optional(),
|
|
30
31
|
roleIds: z.array(z.string().uuid()).optional(),
|
|
31
32
|
}).passthrough()
|
|
@@ -34,8 +35,18 @@ const rawBodySchema = z.object({}).passthrough()
|
|
|
34
35
|
|
|
35
36
|
const passwordSchema = buildPasswordSchema()
|
|
36
37
|
|
|
38
|
+
const displayNameSchema = z.preprocess(
|
|
39
|
+
(value) => {
|
|
40
|
+
if (typeof value !== 'string') return value
|
|
41
|
+
const trimmed = value.trim()
|
|
42
|
+
return trimmed.length ? trimmed : undefined
|
|
43
|
+
},
|
|
44
|
+
z.string().trim().min(1).max(120).optional(),
|
|
45
|
+
)
|
|
46
|
+
|
|
37
47
|
const userCreateSchema = z.object({
|
|
38
48
|
email: z.string().email(),
|
|
49
|
+
name: displayNameSchema,
|
|
39
50
|
password: passwordSchema.optional(),
|
|
40
51
|
sendInviteEmail: z.boolean().optional(),
|
|
41
52
|
organizationId: z.string().uuid(),
|
|
@@ -48,6 +59,7 @@ const userCreateSchema = z.object({
|
|
|
48
59
|
const userUpdateSchema = z.object({
|
|
49
60
|
id: z.string().uuid(),
|
|
50
61
|
email: z.string().email().optional(),
|
|
62
|
+
name: displayNameSchema,
|
|
51
63
|
password: passwordSchema.optional(),
|
|
52
64
|
organizationId: z.string().uuid().optional(),
|
|
53
65
|
roles: z.array(z.string()).optional(),
|
|
@@ -56,6 +68,7 @@ const userUpdateSchema = z.object({
|
|
|
56
68
|
const userListItemSchema = z.object({
|
|
57
69
|
id: z.string().uuid(),
|
|
58
70
|
email: z.string().email(),
|
|
71
|
+
name: z.string().nullable(),
|
|
59
72
|
organizationId: z.string().uuid().nullable(),
|
|
60
73
|
organizationName: z.string().nullable(),
|
|
61
74
|
tenantId: z.string().uuid().nullable(),
|
|
@@ -141,6 +154,7 @@ export async function GET(req: Request) {
|
|
|
141
154
|
page: url.searchParams.get('page') || undefined,
|
|
142
155
|
pageSize: url.searchParams.get('pageSize') || undefined,
|
|
143
156
|
search: url.searchParams.get('search') || undefined,
|
|
157
|
+
name: url.searchParams.get('name') || undefined,
|
|
144
158
|
organizationId: url.searchParams.get('organizationId') || undefined,
|
|
145
159
|
roleIds: rawRoleIds.length ? rawRoleIds : undefined,
|
|
146
160
|
})
|
|
@@ -157,7 +171,7 @@ export async function GET(req: Request) {
|
|
|
157
171
|
} catch (err) {
|
|
158
172
|
console.error('users: failed to resolve rbac', err)
|
|
159
173
|
}
|
|
160
|
-
const { id, page, pageSize, search, organizationId, roleIds } = parsed.data
|
|
174
|
+
const { id, page, pageSize, search, name, organizationId, roleIds } = parsed.data
|
|
161
175
|
const filters: any[] = [{ deletedAt: null }]
|
|
162
176
|
const actorTenantId = auth.tenantId ? String(auth.tenantId) : null
|
|
163
177
|
if (!isSuperAdmin) {
|
|
@@ -167,6 +181,10 @@ export async function GET(req: Request) {
|
|
|
167
181
|
filters.push({ tenantId: actorTenantId })
|
|
168
182
|
}
|
|
169
183
|
if (organizationId) filters.push({ organizationId })
|
|
184
|
+
const trimmedName = typeof name === 'string' ? name.trim() : ''
|
|
185
|
+
if (trimmedName) {
|
|
186
|
+
filters.push({ name: { $ilike: `%${escapeLikePattern(trimmedName)}%` } })
|
|
187
|
+
}
|
|
170
188
|
let idFilter: Set<string> | null = id ? new Set([id]) : null
|
|
171
189
|
if (Array.isArray(roleIds) && roleIds.length > 0) {
|
|
172
190
|
const uniqueRoleIds = Array.from(new Set(roleIds))
|
|
@@ -345,6 +363,7 @@ export async function GET(req: Request) {
|
|
|
345
363
|
return {
|
|
346
364
|
id: uid,
|
|
347
365
|
email: String(u.email),
|
|
366
|
+
name: u.name ? String(u.name) : null,
|
|
348
367
|
organizationId: orgId,
|
|
349
368
|
organizationName: orgId ? orgMap[orgId] ?? orgId : null,
|
|
350
369
|
tenantId: u.tenantId ? String(u.tenantId) : null,
|
|
@@ -475,7 +494,7 @@ export const openApi: OpenApiRouteDoc = {
|
|
|
475
494
|
},
|
|
476
495
|
POST: {
|
|
477
496
|
summary: 'Create user',
|
|
478
|
-
description: 'Creates a new confirmed user within the specified organization and optional roles.',
|
|
497
|
+
description: 'Creates a new confirmed user within the specified organization, optional display name, and optional roles.',
|
|
479
498
|
requestBody: {
|
|
480
499
|
contentType: 'application/json',
|
|
481
500
|
schema: userCreateSchema,
|
|
@@ -495,7 +514,7 @@ export const openApi: OpenApiRouteDoc = {
|
|
|
495
514
|
},
|
|
496
515
|
PUT: {
|
|
497
516
|
summary: 'Update user',
|
|
498
|
-
description: 'Updates profile fields, organization assignment, credentials, or role memberships.',
|
|
517
|
+
description: 'Updates profile fields including display name, organization assignment, credentials, or role memberships.',
|
|
499
518
|
requestBody: {
|
|
500
519
|
contentType: 'application/json',
|
|
501
520
|
schema: userUpdateSchema,
|
|
@@ -19,6 +19,7 @@ import { UserConsentsPanel } from '@open-mercato/core/modules/auth/components/Us
|
|
|
19
19
|
|
|
20
20
|
type EditUserFormValues = {
|
|
21
21
|
email: string
|
|
22
|
+
name: string
|
|
22
23
|
password: string
|
|
23
24
|
tenantId: string | null
|
|
24
25
|
organizationId: string | null
|
|
@@ -28,6 +29,7 @@ type EditUserFormValues = {
|
|
|
28
29
|
type LoadedUser = {
|
|
29
30
|
id: string
|
|
30
31
|
email: string
|
|
32
|
+
name: string | null
|
|
31
33
|
organizationId: string | null
|
|
32
34
|
tenantId: string | null
|
|
33
35
|
tenantName: string | null
|
|
@@ -40,6 +42,7 @@ type LoadedUser = {
|
|
|
40
42
|
type UserApiItem = {
|
|
41
43
|
id?: string | null
|
|
42
44
|
email?: string | null
|
|
45
|
+
name?: string | null
|
|
43
46
|
organizationId?: string | null
|
|
44
47
|
tenantId?: string | null
|
|
45
48
|
tenantName?: string | null
|
|
@@ -193,6 +196,7 @@ export default function EditUserPage({ params }: { params?: { id?: string } }) {
|
|
|
193
196
|
setInitialUser({
|
|
194
197
|
id: item.id ? String(item.id) : String(id),
|
|
195
198
|
email: item.email ? String(item.email) : '',
|
|
199
|
+
name: item.name ? String(item.name) : null,
|
|
196
200
|
organizationId: item.organizationId ? String(item.organizationId) : null,
|
|
197
201
|
tenantId: item.tenantId ? String(item.tenantId) : null,
|
|
198
202
|
tenantName: item.tenantName ? String(item.tenantName) : null,
|
|
@@ -261,6 +265,7 @@ export default function EditUserPage({ params }: { params?: { id?: string } }) {
|
|
|
261
265
|
const fields: CrudField[] = React.useMemo(() => {
|
|
262
266
|
const items: CrudField[] = [
|
|
263
267
|
{ id: 'email', label: t('auth.users.form.field.email', 'Email'), type: 'text', required: true },
|
|
268
|
+
{ id: 'name', label: t('auth.users.form.field.name', 'Display name'), type: 'text' },
|
|
264
269
|
{
|
|
265
270
|
id: 'password',
|
|
266
271
|
label: userHasPassword
|
|
@@ -327,7 +332,7 @@ export default function EditUserPage({ params }: { params?: { id?: string } }) {
|
|
|
327
332
|
}, [actorIsSuperAdmin, loadRoleOptions, passwordDescription, preloadedTenants, selectedOrgId, selectedTenantId, t, userHasPassword])
|
|
328
333
|
|
|
329
334
|
const detailFieldIds = React.useMemo(() => {
|
|
330
|
-
const base: string[] = ['email', 'password', 'organizationId', 'roles']
|
|
335
|
+
const base: string[] = ['email', 'name', 'password', 'organizationId', 'roles']
|
|
331
336
|
if (actorIsSuperAdmin) base.splice(2, 0, 'tenantId')
|
|
332
337
|
return base
|
|
333
338
|
}, [actorIsSuperAdmin])
|
|
@@ -382,6 +387,7 @@ export default function EditUserPage({ params }: { params?: { id?: string } }) {
|
|
|
382
387
|
if (initialUser) {
|
|
383
388
|
return {
|
|
384
389
|
email: initialUser.email,
|
|
390
|
+
name: initialUser.name ?? '',
|
|
385
391
|
password: '',
|
|
386
392
|
tenantId: initialUser.tenantId,
|
|
387
393
|
organizationId: initialUser.organizationId,
|
|
@@ -391,6 +397,7 @@ export default function EditUserPage({ params }: { params?: { id?: string } }) {
|
|
|
391
397
|
}
|
|
392
398
|
return {
|
|
393
399
|
email: '',
|
|
400
|
+
name: '',
|
|
394
401
|
password: '',
|
|
395
402
|
tenantId: selectedTenantId ?? null,
|
|
396
403
|
organizationId: null,
|
|
@@ -438,6 +445,7 @@ export default function EditUserPage({ params }: { params?: { id?: string } }) {
|
|
|
438
445
|
const payload = {
|
|
439
446
|
id: id ? String(id) : '',
|
|
440
447
|
email: values.email,
|
|
448
|
+
name: typeof values.name === 'string' && values.name.trim().length ? values.name.trim() : undefined,
|
|
441
449
|
password: values.password && values.password.trim() ? values.password : undefined,
|
|
442
450
|
organizationId: values.organizationId ? values.organizationId : undefined,
|
|
443
451
|
roles: Array.isArray(values.roles) ? values.roles : [],
|
|
@@ -17,6 +17,7 @@ import { formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/sha
|
|
|
17
17
|
|
|
18
18
|
type CreateUserFormValues = {
|
|
19
19
|
email: string
|
|
20
|
+
name: string
|
|
20
21
|
password: string
|
|
21
22
|
tenantId: string | null
|
|
22
23
|
organizationId: string | null
|
|
@@ -177,6 +178,7 @@ export default function CreateUserPage() {
|
|
|
177
178
|
const fields: CrudField[] = React.useMemo(() => {
|
|
178
179
|
const items: CrudField[] = [
|
|
179
180
|
{ id: 'email', label: t('auth.users.form.field.email', 'Email'), type: 'text', required: true },
|
|
181
|
+
{ id: 'name', label: t('auth.users.form.field.name', 'Display name'), type: 'text' },
|
|
180
182
|
{
|
|
181
183
|
id: 'sendInviteEmail',
|
|
182
184
|
label: t('auth.users.form.field.sendInviteEmail', 'Send password setup link via email'),
|
|
@@ -251,8 +253,8 @@ export default function CreateUserPage() {
|
|
|
251
253
|
|
|
252
254
|
const detailFieldIds = React.useMemo(() => {
|
|
253
255
|
const base: string[] = sendInviteEmail
|
|
254
|
-
? ['email', 'sendInviteEmail', 'organizationId', 'roles']
|
|
255
|
-
: ['email', 'sendInviteEmail', 'password', 'organizationId', 'roles']
|
|
256
|
+
? ['email', 'name', 'sendInviteEmail', 'organizationId', 'roles']
|
|
257
|
+
: ['email', 'name', 'sendInviteEmail', 'password', 'organizationId', 'roles']
|
|
256
258
|
if (actorIsSuperAdmin) {
|
|
257
259
|
const orgIdx = base.indexOf('organizationId')
|
|
258
260
|
base.splice(orgIdx, 0, 'tenantId')
|
|
@@ -294,6 +296,7 @@ export default function CreateUserPage() {
|
|
|
294
296
|
const initialValues = React.useMemo<Partial<CreateUserFormValues>>(
|
|
295
297
|
() => ({
|
|
296
298
|
email: '',
|
|
299
|
+
name: '',
|
|
297
300
|
password: '',
|
|
298
301
|
tenantId: null,
|
|
299
302
|
organizationId: null,
|
|
@@ -323,6 +326,7 @@ export default function CreateUserPage() {
|
|
|
323
326
|
const customFields = collectCustomFieldValues(values)
|
|
324
327
|
const payload: Record<string, unknown> = {
|
|
325
328
|
email: values.email,
|
|
329
|
+
name: typeof values.name === 'string' && values.name.trim().length ? values.name.trim() : undefined,
|
|
326
330
|
organizationId: values.organizationId ? values.organizationId : null,
|
|
327
331
|
roles: Array.isArray(values.roles) ? values.roles : [],
|
|
328
332
|
...(Object.keys(customFields).length ? { customFields } : {}),
|
|
@@ -3,7 +3,7 @@ import * as React from 'react'
|
|
|
3
3
|
import Link from 'next/link'
|
|
4
4
|
import { useSearchParams } from 'next/navigation'
|
|
5
5
|
import { Page, PageBody } from '@open-mercato/ui/backend/Page'
|
|
6
|
-
import { DataTable } from '@open-mercato/ui/backend/DataTable'
|
|
6
|
+
import { DataTable, type DataTableExportFormat } from '@open-mercato/ui/backend/DataTable'
|
|
7
7
|
import type { ColumnDef, SortingState } from '@tanstack/react-table'
|
|
8
8
|
import type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'
|
|
9
9
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
@@ -20,6 +20,7 @@ import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
|
|
|
20
20
|
type Row = {
|
|
21
21
|
id: string
|
|
22
22
|
email: string
|
|
23
|
+
name: string | null
|
|
23
24
|
organizationId: string | null
|
|
24
25
|
organizationName?: string | null
|
|
25
26
|
tenantId: string | null
|
|
@@ -216,6 +217,12 @@ export default function UsersListPage() {
|
|
|
216
217
|
}, [applyRoleOptions])
|
|
217
218
|
|
|
218
219
|
const filters = React.useMemo<FilterDef[]>(() => [
|
|
220
|
+
{
|
|
221
|
+
id: 'name',
|
|
222
|
+
label: t('auth.users.list.filters.name', 'Display name'),
|
|
223
|
+
type: 'text',
|
|
224
|
+
placeholder: t('auth.users.list.filters.namePlaceholder', 'Filter by display name'),
|
|
225
|
+
},
|
|
219
226
|
{
|
|
220
227
|
id: 'organizationId',
|
|
221
228
|
label: t('auth.users.list.filters.organization', 'Organization'),
|
|
@@ -258,6 +265,8 @@ export default function UsersListPage() {
|
|
|
258
265
|
|
|
259
266
|
const handleFiltersApply = React.useCallback((values: FilterValues) => {
|
|
260
267
|
const next: FilterValues = {}
|
|
268
|
+
const name = typeof values.name === 'string' ? values.name.trim() : ''
|
|
269
|
+
if (name) next.name = name
|
|
261
270
|
const org = typeof values.organizationId === 'string' ? values.organizationId.trim() : ''
|
|
262
271
|
if (org) next.organizationId = org
|
|
263
272
|
const rawRoles = Array.isArray(values.roles) ? (values.roles as string[]) : []
|
|
@@ -274,17 +283,63 @@ export default function UsersListPage() {
|
|
|
274
283
|
setPage(1)
|
|
275
284
|
}, [])
|
|
276
285
|
|
|
286
|
+
const exportParams = React.useMemo(() => {
|
|
287
|
+
const name = typeof filterValues.name === 'string' ? filterValues.name.trim() : ''
|
|
288
|
+
return {
|
|
289
|
+
page,
|
|
290
|
+
pageSize: 50,
|
|
291
|
+
search,
|
|
292
|
+
name: name.length ? name : undefined,
|
|
293
|
+
organizationId,
|
|
294
|
+
roleIds: normalizedRoleIds,
|
|
295
|
+
}
|
|
296
|
+
}, [filterValues.name, normalizedRoleIds, organizationId, page, search])
|
|
297
|
+
|
|
298
|
+
const exportConfig = React.useMemo(() => ({
|
|
299
|
+
view: {
|
|
300
|
+
getUrl: (format: DataTableExportFormat) => {
|
|
301
|
+
const params = new URLSearchParams()
|
|
302
|
+
params.set('page', String(exportParams.page))
|
|
303
|
+
params.set('pageSize', String(exportParams.pageSize))
|
|
304
|
+
if (exportParams.search) params.set('search', exportParams.search)
|
|
305
|
+
if (exportParams.name) params.set('name', exportParams.name)
|
|
306
|
+
if (exportParams.organizationId) params.set('organizationId', exportParams.organizationId)
|
|
307
|
+
for (const roleId of exportParams.roleIds) params.append('roleId', roleId)
|
|
308
|
+
params.set('format', format)
|
|
309
|
+
params.set('exportScope', 'view')
|
|
310
|
+
return `/api/auth/users?${params.toString()}`
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
full: {
|
|
314
|
+
getUrl: (format: DataTableExportFormat) => {
|
|
315
|
+
const params = new URLSearchParams()
|
|
316
|
+
params.set('page', String(exportParams.page))
|
|
317
|
+
params.set('pageSize', String(exportParams.pageSize))
|
|
318
|
+
if (exportParams.search) params.set('search', exportParams.search)
|
|
319
|
+
if (exportParams.name) params.set('name', exportParams.name)
|
|
320
|
+
if (exportParams.organizationId) params.set('organizationId', exportParams.organizationId)
|
|
321
|
+
for (const roleId of exportParams.roleIds) params.append('roleId', roleId)
|
|
322
|
+
params.set('format', format)
|
|
323
|
+
params.set('exportScope', 'full')
|
|
324
|
+
params.set('all', 'true')
|
|
325
|
+
return `/api/auth/users?${params.toString()}`
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
}), [exportParams])
|
|
329
|
+
|
|
277
330
|
const params = React.useMemo(() => {
|
|
278
331
|
const p = new URLSearchParams()
|
|
279
332
|
p.set('page', String(page))
|
|
280
333
|
p.set('pageSize', '50')
|
|
281
334
|
if (search) p.set('search', search)
|
|
335
|
+
const name = typeof filterValues.name === 'string' ? filterValues.name.trim() : ''
|
|
336
|
+
if (name) p.set('name', name)
|
|
282
337
|
if (organizationId) p.set('organizationId', organizationId)
|
|
283
338
|
if (normalizedRoleIds.length) {
|
|
284
339
|
for (const id of normalizedRoleIds) p.append('roleId', id)
|
|
285
340
|
}
|
|
286
341
|
return p.toString()
|
|
287
|
-
}, [page, search, organizationId, normalizedRoleIds])
|
|
342
|
+
}, [filterValues.name, page, search, organizationId, normalizedRoleIds])
|
|
288
343
|
|
|
289
344
|
const { data: usersData, isLoading } = useQuery({
|
|
290
345
|
queryKey: ['users', params, scopeVersion],
|
|
@@ -315,6 +370,7 @@ export default function UsersListPage() {
|
|
|
315
370
|
const columns = React.useMemo<ColumnDef<Row>[]>(() => {
|
|
316
371
|
const base: ColumnDef<Row>[] = [
|
|
317
372
|
{ accessorKey: 'email', header: 'Email' },
|
|
373
|
+
{ accessorKey: 'name', header: t('auth.users.list.columns.name', 'Display name') },
|
|
318
374
|
{ accessorKey: 'organizationName', header: 'Organization' },
|
|
319
375
|
{ accessorKey: 'roles', header: 'Roles', cell: ({ row }) => (row.original.roles || []).join(', ') },
|
|
320
376
|
]
|
|
@@ -322,7 +378,7 @@ export default function UsersListPage() {
|
|
|
322
378
|
base.splice(1, 0, { accessorKey: 'tenantName', header: 'Tenant' })
|
|
323
379
|
}
|
|
324
380
|
return base
|
|
325
|
-
}, [showTenantColumn])
|
|
381
|
+
}, [showTenantColumn, t])
|
|
326
382
|
|
|
327
383
|
const handleDelete = React.useCallback(async (row: Row) => {
|
|
328
384
|
const confirmed = await confirm({
|
|
@@ -356,6 +412,7 @@ export default function UsersListPage() {
|
|
|
356
412
|
)}
|
|
357
413
|
columns={columns}
|
|
358
414
|
data={rowsWithOrgNames}
|
|
415
|
+
exporter={exportConfig}
|
|
359
416
|
searchValue={search}
|
|
360
417
|
onSearchChange={handleSearchChange}
|
|
361
418
|
filters={filters}
|
|
@@ -75,8 +75,18 @@ type UserSnapshots = {
|
|
|
75
75
|
|
|
76
76
|
const passwordSchema = buildPasswordSchema()
|
|
77
77
|
|
|
78
|
+
const displayNameSchema = z.preprocess(
|
|
79
|
+
(value) => {
|
|
80
|
+
if (typeof value !== 'string') return value
|
|
81
|
+
const trimmed = value.trim()
|
|
82
|
+
return trimmed.length ? trimmed : undefined
|
|
83
|
+
},
|
|
84
|
+
z.string().trim().min(1).max(120).optional(),
|
|
85
|
+
)
|
|
86
|
+
|
|
78
87
|
const createSchema = z.object({
|
|
79
88
|
email: z.string().email(),
|
|
89
|
+
name: displayNameSchema,
|
|
80
90
|
password: passwordSchema.optional(),
|
|
81
91
|
sendInviteEmail: z.boolean().optional(),
|
|
82
92
|
organizationId: z.string().uuid(),
|
|
@@ -89,6 +99,7 @@ const createSchema = z.object({
|
|
|
89
99
|
const updateSchema = z.object({
|
|
90
100
|
id: z.string().uuid(),
|
|
91
101
|
email: z.string().email().optional(),
|
|
102
|
+
name: displayNameSchema,
|
|
92
103
|
password: passwordSchema.optional(),
|
|
93
104
|
organizationId: z.string().uuid().optional(),
|
|
94
105
|
roles: z.array(z.string()).optional(),
|
|
@@ -196,6 +207,7 @@ const createUserCommand: CommandHandler<Record<string, unknown>, CreateUserResul
|
|
|
196
207
|
entity: User,
|
|
197
208
|
data: {
|
|
198
209
|
email: parsed.email,
|
|
210
|
+
name: parsed.name,
|
|
199
211
|
emailHash,
|
|
200
212
|
passwordHash,
|
|
201
213
|
isConfirmed: true,
|
|
@@ -451,6 +463,9 @@ const updateUserCommand: CommandHandler<Record<string, unknown>, User> = {
|
|
|
451
463
|
entity.email = parsed.email
|
|
452
464
|
entity.emailHash = emailHash
|
|
453
465
|
}
|
|
466
|
+
if (parsed.name !== undefined) {
|
|
467
|
+
entity.name = parsed.name
|
|
468
|
+
}
|
|
454
469
|
if (parsed.organizationId !== undefined) {
|
|
455
470
|
entity.organizationId = parsed.organizationId
|
|
456
471
|
entity.tenantId = tenantId ?? null
|
|
@@ -103,6 +103,14 @@ export const sidebarPreferencesInputSchema = z.object({
|
|
|
103
103
|
// Optional helpers for CLI or admin forms
|
|
104
104
|
export const userCreateSchema = z.object({
|
|
105
105
|
email: z.string().email(),
|
|
106
|
+
name: z.preprocess(
|
|
107
|
+
(value) => {
|
|
108
|
+
if (typeof value !== 'string') return value
|
|
109
|
+
const trimmed = value.trim()
|
|
110
|
+
return trimmed.length ? trimmed : undefined
|
|
111
|
+
},
|
|
112
|
+
z.string().trim().min(1).max(120).optional(),
|
|
113
|
+
),
|
|
106
114
|
password: passwordSchema.optional(),
|
|
107
115
|
sendInviteEmail: z.boolean().optional(),
|
|
108
116
|
tenantId: z.string().uuid().optional(),
|
|
@@ -172,6 +172,7 @@
|
|
|
172
172
|
"auth.users.form.errors.notFound": "Benutzer nicht gefunden",
|
|
173
173
|
"auth.users.form.errors.widgetsAssign": "Dashboard-Widgets konnten dem neuen Benutzer nicht zugewiesen werden",
|
|
174
174
|
"auth.users.form.field.email": "E-Mail",
|
|
175
|
+
"auth.users.form.field.name": "Anzeigename",
|
|
175
176
|
"auth.users.form.field.newPassword": "Neues Passwort",
|
|
176
177
|
"auth.users.form.field.organization": "Organisation",
|
|
177
178
|
"auth.users.form.field.password": "Passwort",
|
|
@@ -190,8 +191,11 @@
|
|
|
190
191
|
"auth.users.form.loading": "Benutzerdaten werden geladen...",
|
|
191
192
|
"auth.users.form.title.create": "Benutzer erstellen",
|
|
192
193
|
"auth.users.form.title.edit": "Benutzer bearbeiten",
|
|
194
|
+
"auth.users.list.columns.name": "Anzeigename",
|
|
193
195
|
"auth.users.list.error.delete": "Benutzer konnte nicht gelöscht werden",
|
|
194
196
|
"auth.users.list.error.load": "Benutzer konnten nicht geladen werden",
|
|
197
|
+
"auth.users.list.filters.name": "Anzeigename",
|
|
198
|
+
"auth.users.list.filters.namePlaceholder": "Nach Anzeigename filtern",
|
|
195
199
|
"auth.users.widgets.errors.load": "Dashboard-Widgets können nicht geladen werden. Sie können sie später auf der Benutzerseite konfigurieren.",
|
|
196
200
|
"auth.users.widgets.loading": "Widgets werden geladen…",
|
|
197
201
|
"auth.users.widgets.mode.hint": "Neue Benutzer erben Widgets von ihren zugewiesenen Rollen. Überschreiben Sie die Auswahl, um eine eigene Zusammenstellung festzulegen.",
|