@open-mercato/core 0.8.1-develop.7254.1.ab24f26208 → 0.8.1-develop.7263.1.bbc6db3437
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js +11 -3
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/users.js +27 -14
- package/dist/modules/customer_accounts/api/admin/users.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.js +9 -0
- package/dist/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/PortalUserDetailPageClient.js +8 -8
- package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/PortalUserDetailPageClient.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/map/page.js +10 -2
- package/dist/modules/customers/backend/customers/deals/map/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +10 -2
- package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js +41 -66
- package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js +6 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
- package/dist/modules/customers/components/formConfig.js +3 -3
- package/dist/modules/customers/components/formConfig.js.map +2 -2
- package/dist/modules/customers/data/validators.js +3 -1
- package/dist/modules/customers/data/validators.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +19 -5
- package/src/modules/catalog/i18n/de.json +1 -0
- package/src/modules/catalog/i18n/en.json +1 -0
- package/src/modules/catalog/i18n/es.json +1 -0
- package/src/modules/catalog/i18n/ko.json +1 -0
- package/src/modules/catalog/i18n/pl.json +1 -0
- package/src/modules/customer_accounts/api/admin/users.ts +30 -14
- package/src/modules/customer_accounts/backend/customer_accounts/users/PortalUsersPageClient.tsx +11 -0
- package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/PortalUserDetailPageClient.tsx +11 -11
- package/src/modules/customer_accounts/i18n/de.json +1 -0
- package/src/modules/customer_accounts/i18n/en.json +1 -0
- package/src/modules/customer_accounts/i18n/es.json +1 -0
- package/src/modules/customer_accounts/i18n/ko.json +1 -0
- package/src/modules/customer_accounts/i18n/pl.json +1 -0
- package/src/modules/customers/backend/customers/deals/map/page.tsx +13 -2
- package/src/modules/customers/backend/customers/deals/page.tsx +13 -2
- package/src/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.tsx +69 -56
- package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +8 -2
- package/src/modules/customers/components/formConfig.tsx +3 -3
- package/src/modules/customers/data/validators.ts +2 -1
- package/src/modules/customers/i18n/de.json +1 -0
- package/src/modules/customers/i18n/en.json +1 -0
- package/src/modules/customers/i18n/es.json +1 -0
- package/src/modules/customers/i18n/ko.json +1 -0
- package/src/modules/customers/i18n/pl.json +1 -0
|
@@ -14,11 +14,22 @@ import {
|
|
|
14
14
|
import { SearchInput } from '@open-mercato/ui/primitives/search-input'
|
|
15
15
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
16
16
|
import { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'
|
|
17
|
-
import {
|
|
17
|
+
import { useRegisteredComponent } from '@open-mercato/ui/backend/injection/useRegisteredComponent'
|
|
18
|
+
import {
|
|
19
|
+
ViewTabsRow,
|
|
20
|
+
VIEW_TABS_ROW_COMPONENT_ID,
|
|
21
|
+
type ViewTabsRowProps,
|
|
22
|
+
} from '../pipeline/components/ViewTabsRow'
|
|
18
23
|
import { DealsMapView } from './components/DealsMapView'
|
|
19
24
|
|
|
20
25
|
export default function DealsMapPage(): React.ReactElement {
|
|
21
26
|
const t = useT()
|
|
27
|
+
// Resolved through the component registry so downstream apps can hide a view
|
|
28
|
+
// (or replace the whole switcher) without forking this page.
|
|
29
|
+
const DealsViewTabsRow = useRegisteredComponent<ViewTabsRowProps>(
|
|
30
|
+
VIEW_TABS_ROW_COMPONENT_ID,
|
|
31
|
+
ViewTabsRow,
|
|
32
|
+
)
|
|
22
33
|
const [search, setSearch] = React.useState('')
|
|
23
34
|
|
|
24
35
|
return (
|
|
@@ -68,7 +79,7 @@ export default function DealsMapPage(): React.ReactElement {
|
|
|
68
79
|
</div>
|
|
69
80
|
</div>
|
|
70
81
|
|
|
71
|
-
<
|
|
82
|
+
<DealsViewTabsRow active="map" className="mt-4" />
|
|
72
83
|
|
|
73
84
|
<DealsMapView search={search} />
|
|
74
85
|
</PageBody>
|
|
@@ -30,7 +30,12 @@ import { SimpleTooltip } from '@open-mercato/ui/primitives/tooltip'
|
|
|
30
30
|
import { Briefcase, AlertTriangle, X } from 'lucide-react'
|
|
31
31
|
import { isLostDealStatus, isWonDealStatus } from '../../../lib/dealStatus'
|
|
32
32
|
import { formatRelativeTime } from '@open-mercato/shared/lib/time'
|
|
33
|
-
import {
|
|
33
|
+
import { useRegisteredComponent } from '@open-mercato/ui/backend/injection/useRegisteredComponent'
|
|
34
|
+
import {
|
|
35
|
+
ViewTabsRow,
|
|
36
|
+
VIEW_TABS_ROW_COMPONENT_ID,
|
|
37
|
+
type ViewTabsRowProps,
|
|
38
|
+
} from './pipeline/components/ViewTabsRow'
|
|
34
39
|
import { DealsKpiStrip } from '../../../components/DealsKpiStrip'
|
|
35
40
|
import { E } from '#generated/entities.ids.generated'
|
|
36
41
|
import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
|
|
@@ -195,6 +200,12 @@ function formatGroupedAmount(amount: number | null | undefined): string | null {
|
|
|
195
200
|
|
|
196
201
|
export default function CustomersDealsPage() {
|
|
197
202
|
const t = useT()
|
|
203
|
+
// Resolved through the component registry so downstream apps can hide a view
|
|
204
|
+
// (or replace the whole switcher) without forking this page.
|
|
205
|
+
const DealsViewTabsRow = useRegisteredComponent<ViewTabsRowProps>(
|
|
206
|
+
VIEW_TABS_ROW_COMPONENT_ID,
|
|
207
|
+
ViewTabsRow,
|
|
208
|
+
)
|
|
198
209
|
const locale = useLocale()
|
|
199
210
|
const { confirm, ConfirmDialogElement } = useConfirmDialog()
|
|
200
211
|
const router = useRouter()
|
|
@@ -1064,7 +1075,7 @@ export default function CustomersDealsPage() {
|
|
|
1064
1075
|
return (
|
|
1065
1076
|
<Page>
|
|
1066
1077
|
<PageBody>
|
|
1067
|
-
<
|
|
1078
|
+
<DealsViewTabsRow active="list" className="mb-4" />
|
|
1068
1079
|
<DealsKpiStrip
|
|
1069
1080
|
ownerNames={ownerNames}
|
|
1070
1081
|
stageDictionary={dictionaryMaps['pipeline-stages'] ?? {}}
|
|
@@ -7,24 +7,76 @@ import { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'
|
|
|
7
7
|
|
|
8
8
|
export type KanbanView = 'kanban' | 'list' | 'map'
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Canonical left-to-right order of the deals views. `visibleViews` selects a
|
|
12
|
+
* subset of these; it never reorders them, so a host that hides one view keeps
|
|
13
|
+
* the same tab order as every other deployment.
|
|
14
|
+
*/
|
|
15
|
+
export const DEALS_VIEWS: readonly KanbanView[] = ['kanban', 'list', 'map']
|
|
16
|
+
|
|
17
|
+
export type ViewTabsRowProps = {
|
|
11
18
|
active: KanbanView
|
|
12
19
|
className?: string
|
|
20
|
+
/**
|
|
21
|
+
* Views exposed in the tab row. Defaults to all of `DEALS_VIEWS`; pass a
|
|
22
|
+
* subset to hide a view a deployment has no use for (e.g. omit `'map'` when
|
|
23
|
+
* deal addresses are never geocoded). Unknown and duplicated entries are
|
|
24
|
+
* ignored, and `active` is always rendered — a tab row that omits the page it
|
|
25
|
+
* is sitting on would show no selected tab at all.
|
|
26
|
+
*/
|
|
27
|
+
visibleViews?: readonly KanbanView[]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Registered component id for the deals view switcher. Downstream apps can
|
|
32
|
+
* target it from `widgets/components.ts` to replace or wrap the row, or — the
|
|
33
|
+
* reason it is resolved through the registry at all — to transform its props
|
|
34
|
+
* from one place instead of forking each of the three pages that render it:
|
|
35
|
+
*
|
|
36
|
+
* ```ts
|
|
37
|
+
* export const componentOverrides: ComponentOverride[] = [{
|
|
38
|
+
* target: { componentId: 'section:customers.deals.viewTabs' },
|
|
39
|
+
* priority: 50,
|
|
40
|
+
* metadata: { module: 'my_module' },
|
|
41
|
+
* propsTransform: (props) => ({ ...props, visibleViews: ['kanban', 'list'] }),
|
|
42
|
+
* }]
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* Hiding a tab does not unpublish its route; an app that also wants the page
|
|
46
|
+
* gone disables it through `entry.overrides.pages` (#5923).
|
|
47
|
+
*/
|
|
48
|
+
export const VIEW_TABS_ROW_COMPONENT_ID = 'section:customers.deals.viewTabs'
|
|
49
|
+
|
|
50
|
+
const VIEW_HREFS: Record<KanbanView, string> = {
|
|
51
|
+
kanban: '/backend/customers/deals/pipeline',
|
|
52
|
+
list: '/backend/customers/deals',
|
|
53
|
+
map: '/backend/customers/deals/map',
|
|
13
54
|
}
|
|
14
55
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Narrows `visibleViews` to the tabs this row renders, in canonical order.
|
|
58
|
+
* Exported so the contract — canonical order, deduplication, unknown entries
|
|
59
|
+
* dropped, `active` always kept — is testable without rendering.
|
|
60
|
+
*/
|
|
61
|
+
export function resolveVisibleViews(
|
|
62
|
+
active: KanbanView,
|
|
63
|
+
visibleViews?: readonly KanbanView[],
|
|
64
|
+
): KanbanView[] {
|
|
65
|
+
if (!visibleViews) return [...DEALS_VIEWS]
|
|
66
|
+
const requested = new Set<KanbanView>(visibleViews.filter((view) => DEALS_VIEWS.includes(view)))
|
|
67
|
+
requested.add(active)
|
|
68
|
+
return DEALS_VIEWS.filter((view) => requested.has(view))
|
|
69
|
+
}
|
|
18
70
|
|
|
19
|
-
export function ViewTabsRow({ active, className }: ViewTabsRowProps): React.ReactElement {
|
|
71
|
+
export function ViewTabsRow({ active, className, visibleViews }: ViewTabsRowProps): React.ReactElement {
|
|
20
72
|
const t = useT()
|
|
21
|
-
const labels = {
|
|
73
|
+
const labels: Record<KanbanView, string> = {
|
|
22
74
|
kanban: translateWithFallback(t, 'customers.deals.kanban.view.kanban', 'Kanban'),
|
|
23
75
|
list: translateWithFallback(t, 'customers.deals.kanban.view.list', 'List'),
|
|
24
76
|
map: translateWithFallback(t, 'customers.deals.kanban.view.map', 'Map'),
|
|
25
77
|
}
|
|
26
78
|
|
|
27
|
-
// Link-based tab row (
|
|
79
|
+
// Link-based tab row (three routes), so the Tabs primitive (state-driven,
|
|
28
80
|
// onValueChange) does not fit — real <Link> semantics must stay. Classes
|
|
29
81
|
// mirror the Tabs underline variant: accent-indigo active border,
|
|
30
82
|
// shadow-focus halo.
|
|
@@ -33,14 +85,9 @@ export function ViewTabsRow({ active, className }: ViewTabsRowProps): React.Reac
|
|
|
33
85
|
const activeTab = 'border-b-2 border-accent-indigo font-semibold text-foreground'
|
|
34
86
|
const inactiveTab = 'border-b-2 border-transparent font-normal text-muted-foreground hover:text-foreground'
|
|
35
87
|
|
|
36
|
-
//
|
|
37
|
-
// so the user can always round-trip between
|
|
38
|
-
|
|
39
|
-
// page — adding it to the list page (item 7 of the SPEC-048 UX review) requires it to
|
|
40
|
-
// navigate back to /pipeline.
|
|
41
|
-
const isKanbanActive = active === 'kanban'
|
|
42
|
-
const isListActive = active === 'list'
|
|
43
|
-
const isMapActive = active === 'map'
|
|
88
|
+
// The active tab renders as a non-navigating `<span>` and every other tab as a
|
|
89
|
+
// `<Link>`, so the user can always round-trip between the views from any page.
|
|
90
|
+
const views = resolveVisibleViews(active, visibleViews)
|
|
44
91
|
|
|
45
92
|
return (
|
|
46
93
|
<div
|
|
@@ -48,60 +95,26 @@ export function ViewTabsRow({ active, className }: ViewTabsRowProps): React.Reac
|
|
|
48
95
|
aria-label={translateWithFallback(t, 'customers.deals.kanban.view.tablistLabel', 'Deals views')}
|
|
49
96
|
className={`flex items-end gap-1 border-b border-border ${className ?? ''}`.trim()}
|
|
50
97
|
>
|
|
51
|
-
{
|
|
52
|
-
<span
|
|
53
|
-
role="tab"
|
|
54
|
-
aria-selected={true}
|
|
55
|
-
className={`${baseTab} ${activeTab}`}
|
|
56
|
-
>
|
|
57
|
-
{labels.kanban}
|
|
58
|
-
</span>
|
|
59
|
-
) : (
|
|
60
|
-
<Link
|
|
61
|
-
href={VIEW_KANBAN_HREF}
|
|
62
|
-
role="tab"
|
|
63
|
-
aria-selected={false}
|
|
64
|
-
className={`${baseTab} ${inactiveTab}`}
|
|
65
|
-
>
|
|
66
|
-
{labels.kanban}
|
|
67
|
-
</Link>
|
|
68
|
-
)}
|
|
69
|
-
{isListActive ? (
|
|
70
|
-
<span
|
|
71
|
-
role="tab"
|
|
72
|
-
aria-selected={true}
|
|
73
|
-
className={`${baseTab} ${activeTab}`}
|
|
74
|
-
>
|
|
75
|
-
{labels.list}
|
|
76
|
-
</span>
|
|
77
|
-
) : (
|
|
78
|
-
<Link
|
|
79
|
-
href={VIEW_LIST_HREF}
|
|
80
|
-
role="tab"
|
|
81
|
-
aria-selected={false}
|
|
82
|
-
className={`${baseTab} ${inactiveTab}`}
|
|
83
|
-
>
|
|
84
|
-
{labels.list}
|
|
85
|
-
</Link>
|
|
86
|
-
)}
|
|
87
|
-
{isMapActive ? (
|
|
98
|
+
{views.map((view) => (view === active ? (
|
|
88
99
|
<span
|
|
100
|
+
key={view}
|
|
89
101
|
role="tab"
|
|
90
102
|
aria-selected={true}
|
|
91
103
|
className={`${baseTab} ${activeTab}`}
|
|
92
104
|
>
|
|
93
|
-
{labels
|
|
105
|
+
{labels[view]}
|
|
94
106
|
</span>
|
|
95
107
|
) : (
|
|
96
108
|
<Link
|
|
97
|
-
|
|
109
|
+
key={view}
|
|
110
|
+
href={VIEW_HREFS[view]}
|
|
98
111
|
role="tab"
|
|
99
112
|
aria-selected={false}
|
|
100
113
|
className={`${baseTab} ${inactiveTab}`}
|
|
101
114
|
>
|
|
102
|
-
{labels
|
|
115
|
+
{labels[view]}
|
|
103
116
|
</Link>
|
|
104
|
-
)}
|
|
117
|
+
)))}
|
|
105
118
|
</div>
|
|
106
119
|
)
|
|
107
120
|
}
|
|
@@ -54,7 +54,7 @@ import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
|
54
54
|
import { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'
|
|
55
55
|
import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
|
|
56
56
|
import type { FilterOptionTone } from '@open-mercato/shared/lib/query/advanced-filter'
|
|
57
|
-
import { ViewTabsRow } from './components/ViewTabsRow'
|
|
57
|
+
import { ViewTabsRow, VIEW_TABS_ROW_COMPONENT_ID, type ViewTabsRowProps } from './components/ViewTabsRow'
|
|
58
58
|
import { LANE_WIDTH_CLASS } from './components/constants'
|
|
59
59
|
import { useCurrencyDictionary } from '../../../../components/detail/hooks/useCurrencyDictionary'
|
|
60
60
|
import { FilterBarRow, type KanbanFilterChip } from './components/FilterBarRow'
|
|
@@ -419,6 +419,12 @@ export default function DealsKanbanPage(): React.ReactElement {
|
|
|
419
419
|
QUICK_DEAL_DIALOG_COMPONENT_ID,
|
|
420
420
|
DefaultQuickDealDialog,
|
|
421
421
|
)
|
|
422
|
+
// Same reason: an app that does not use one of the three views hides it with a
|
|
423
|
+
// props override on this handle instead of forking all three deals pages.
|
|
424
|
+
const DealsViewTabsRow = useRegisteredComponent<ViewTabsRowProps>(
|
|
425
|
+
VIEW_TABS_ROW_COMPONENT_ID,
|
|
426
|
+
ViewTabsRow,
|
|
427
|
+
)
|
|
422
428
|
const router = useRouter()
|
|
423
429
|
const scopeVersion = useOrganizationScopeVersion()
|
|
424
430
|
const queryClient = useQueryClient()
|
|
@@ -2558,7 +2564,7 @@ export default function DealsKanbanPage(): React.ReactElement {
|
|
|
2558
2564
|
return (
|
|
2559
2565
|
<Page>
|
|
2560
2566
|
<PageBody>
|
|
2561
|
-
<
|
|
2567
|
+
<DealsViewTabsRow active="kanban" className="mb-4" />
|
|
2562
2568
|
<div className="flex flex-col gap-2">
|
|
2563
2569
|
<Breadcrumb>
|
|
2564
2570
|
<BreadcrumbList>
|
|
@@ -59,7 +59,7 @@ import {
|
|
|
59
59
|
type CustomerDictionaryKind,
|
|
60
60
|
} from '../lib/dictionaries'
|
|
61
61
|
import { normalizeCustomFieldSubmitValue } from './detail/customFieldUtils'
|
|
62
|
-
import { CUSTOMER_PHONE_INVALID_MESSAGE_KEY } from '../data/validators'
|
|
62
|
+
import { CUSTOMER_PHONE_INVALID_MESSAGE_KEY, CUSTOMER_URL_INVALID_MESSAGE_KEY } from '../data/validators'
|
|
63
63
|
|
|
64
64
|
export const metadata = {
|
|
65
65
|
navHidden: true,
|
|
@@ -1199,7 +1199,7 @@ export const createCompanyFormSchema = () =>
|
|
|
1199
1199
|
websiteUrl: z
|
|
1200
1200
|
.string()
|
|
1201
1201
|
.trim()
|
|
1202
|
-
.url()
|
|
1202
|
+
.url(CUSTOMER_URL_INVALID_MESSAGE_KEY)
|
|
1203
1203
|
.optional()
|
|
1204
1204
|
.or(z.literal(''))
|
|
1205
1205
|
.transform((val) => (val === '' ? undefined : val))
|
|
@@ -1559,7 +1559,7 @@ const clearableUrlField = () =>
|
|
|
1559
1559
|
z
|
|
1560
1560
|
.string()
|
|
1561
1561
|
.trim()
|
|
1562
|
-
.url()
|
|
1562
|
+
.url(CUSTOMER_URL_INVALID_MESSAGE_KEY)
|
|
1563
1563
|
.optional()
|
|
1564
1564
|
.or(z.literal(''))
|
|
1565
1565
|
.transform((val) => (val === '' ? null : val))
|
|
@@ -6,6 +6,7 @@ import { dictionaryEntrySortModeSchema } from '@open-mercato/core/modules/dictio
|
|
|
6
6
|
const uuid = () => z.string().uuid()
|
|
7
7
|
|
|
8
8
|
export const CUSTOMER_PHONE_INVALID_MESSAGE_KEY = 'customers.people.form.primaryPhone.invalid'
|
|
9
|
+
export const CUSTOMER_URL_INVALID_MESSAGE_KEY = 'customers.people.form.websiteUrl.invalid'
|
|
9
10
|
export const ACTIVITY_DATE_REQUIRED_MESSAGE_KEY = 'customers.activities.errors.dateRequired'
|
|
10
11
|
export const ACTIVITY_TIME_REQUIRED_MESSAGE_KEY = 'customers.activities.errors.timeRequired'
|
|
11
12
|
export const ACTIVITY_PHONE_REQUIRED_MESSAGE_KEY = 'customers.activities.errors.phoneRequired'
|
|
@@ -41,7 +42,7 @@ const clearableEmailSchema = z.preprocess(
|
|
|
41
42
|
|
|
42
43
|
const clearableUrlSchema = z.preprocess(
|
|
43
44
|
emptyStringToNull,
|
|
44
|
-
z.string().url().max(300).nullable().optional(),
|
|
45
|
+
z.string().url(CUSTOMER_URL_INVALID_MESSAGE_KEY).max(300).nullable().optional(),
|
|
45
46
|
)
|
|
46
47
|
|
|
47
48
|
// Domain is a plain (non-URL) string that maps to a nullable column, so blanking
|
|
@@ -2352,6 +2352,7 @@
|
|
|
2352
2352
|
"customers.people.form.twitterUrl": "X / Twitter",
|
|
2353
2353
|
"customers.people.form.twitterUrl.placeholder": "https://x.com/beispiel",
|
|
2354
2354
|
"customers.people.form.updateSuccess": "Person aktualisiert.",
|
|
2355
|
+
"customers.people.form.websiteUrl.invalid": "Geben Sie eine gültige URL ein (z. B. https://example.com)",
|
|
2355
2356
|
"customers.people.list.actions.delete": "Löschen",
|
|
2356
2357
|
"customers.people.list.actions.new": "Neue Person",
|
|
2357
2358
|
"customers.people.list.actions.openInNewTab": "In neuem Tab öffnen",
|
|
@@ -2352,6 +2352,7 @@
|
|
|
2352
2352
|
"customers.people.form.twitterUrl": "X / Twitter",
|
|
2353
2353
|
"customers.people.form.twitterUrl.placeholder": "https://x.com/example",
|
|
2354
2354
|
"customers.people.form.updateSuccess": "Person updated.",
|
|
2355
|
+
"customers.people.form.websiteUrl.invalid": "Enter a valid URL (e.g. https://example.com)",
|
|
2355
2356
|
"customers.people.list.actions.delete": "Delete",
|
|
2356
2357
|
"customers.people.list.actions.new": "New Person",
|
|
2357
2358
|
"customers.people.list.actions.openInNewTab": "Open in new tab",
|
|
@@ -2352,6 +2352,7 @@
|
|
|
2352
2352
|
"customers.people.form.twitterUrl": "X / Twitter",
|
|
2353
2353
|
"customers.people.form.twitterUrl.placeholder": "https://x.com/ejemplo",
|
|
2354
2354
|
"customers.people.form.updateSuccess": "Persona actualizada.",
|
|
2355
|
+
"customers.people.form.websiteUrl.invalid": "Introduce una URL válida (p. ej., https://example.com)",
|
|
2355
2356
|
"customers.people.list.actions.delete": "Eliminar",
|
|
2356
2357
|
"customers.people.list.actions.new": "Nueva persona",
|
|
2357
2358
|
"customers.people.list.actions.openInNewTab": "Abrir en nueva pestaña",
|
|
@@ -2352,6 +2352,7 @@
|
|
|
2352
2352
|
"customers.people.form.twitterUrl": "X / Twitter",
|
|
2353
2353
|
"customers.people.form.twitterUrl.placeholder": "https://x.com/example",
|
|
2354
2354
|
"customers.people.form.updateSuccess": "연락처가 업데이트되었습니다.",
|
|
2355
|
+
"customers.people.form.websiteUrl.invalid": "올바른 URL을 입력하세요 (예: https://example.com)",
|
|
2355
2356
|
"customers.people.list.actions.delete": "삭제",
|
|
2356
2357
|
"customers.people.list.actions.new": "새 연락처",
|
|
2357
2358
|
"customers.people.list.actions.openInNewTab": "새 탭에서 열기",
|
|
@@ -2352,6 +2352,7 @@
|
|
|
2352
2352
|
"customers.people.form.twitterUrl": "X / Twitter",
|
|
2353
2353
|
"customers.people.form.twitterUrl.placeholder": "https://x.com/przyklad",
|
|
2354
2354
|
"customers.people.form.updateSuccess": "Osoba została zaktualizowana.",
|
|
2355
|
+
"customers.people.form.websiteUrl.invalid": "Podaj prawidłowy adres URL (np. https://example.com)",
|
|
2355
2356
|
"customers.people.list.actions.delete": "Usuń",
|
|
2356
2357
|
"customers.people.list.actions.new": "Nowa osoba",
|
|
2357
2358
|
"customers.people.list.actions.openInNewTab": "Otwórz w nowej karcie",
|