@open-mercato/core 0.6.6-develop.6317.1.b3be10ab84 → 0.6.6-develop.6330.1.a261878aa8
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/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +7 -12
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +5 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +2 -1
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection/company-users/widget.client.js +2 -1
- package/dist/modules/customer_accounts/widgets/injection/company-users/widget.client.js.map +2 -2
- package/dist/modules/customers/components/detail/EntityTagsDialog.js +76 -67
- package/dist/modules/customers/components/detail/EntityTagsDialog.js.map +3 -3
- package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +4 -3
- package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
- package/dist/modules/data_sync/api/runs.js +1 -0
- package/dist/modules/data_sync/api/runs.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.js +3 -1
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/data/validators.js +1 -0
- package/dist/modules/data_sync/data/validators.js.map +2 -2
- package/dist/modules/data_sync/lib/sync-run-service.js +20 -0
- package/dist/modules/data_sync/lib/sync-run-service.js.map +2 -2
- package/dist/modules/entities/api/records.js +11 -1
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +17 -10
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
- package/dist/modules/messages/api/unread-count/route.js +32 -0
- package/dist/modules/messages/api/unread-count/route.js.map +2 -2
- package/dist/modules/query_index/components/QueryIndexesTable.js +13 -5
- package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
- package/dist/modules/shipping_carriers/i18n/en.js +11 -1
- package/dist/modules/shipping_carriers/i18n/en.js.map +2 -2
- package/dist/modules/shipping_carriers/i18n/pl.js +11 -1
- package/dist/modules/shipping_carriers/i18n/pl.js.map +2 -2
- package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.client.js +25 -0
- package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.client.js.map +7 -0
- package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.js +3 -17
- package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.js.map +2 -2
- package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js +19 -14
- package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +7 -10
- package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +6 -7
- package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +3 -2
- package/src/modules/customer_accounts/widgets/injection/company-users/widget.client.tsx +3 -2
- package/src/modules/customers/components/detail/EntityTagsDialog.tsx +89 -71
- package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +6 -3
- package/src/modules/data_sync/api/runs.ts +1 -0
- package/src/modules/data_sync/backend/data-sync/page.tsx +3 -1
- package/src/modules/data_sync/data/validators.ts +1 -0
- package/src/modules/data_sync/i18n/de.json +1 -0
- package/src/modules/data_sync/i18n/en.json +1 -0
- package/src/modules/data_sync/i18n/es.json +1 -0
- package/src/modules/data_sync/i18n/pl.json +1 -0
- package/src/modules/data_sync/lib/sync-run-service.ts +23 -0
- package/src/modules/entities/api/records.ts +17 -1
- package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +25 -12
- package/src/modules/messages/api/unread-count/route.ts +40 -0
- package/src/modules/query_index/components/QueryIndexesTable.tsx +17 -15
- package/src/modules/shipping_carriers/i18n/de.json +11 -1
- package/src/modules/shipping_carriers/i18n/en.json +11 -1
- package/src/modules/shipping_carriers/i18n/en.ts +10 -0
- package/src/modules/shipping_carriers/i18n/es.json +11 -1
- package/src/modules/shipping_carriers/i18n/pl.json +11 -1
- package/src/modules/shipping_carriers/i18n/pl.ts +10 -0
- package/src/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.client.tsx +35 -0
- package/src/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.tsx +3 -22
- package/src/modules/translations/widgets/injection/translation-manager/widget.client.tsx +30 -13
|
@@ -107,6 +107,8 @@ const listRecordsQuerySchema = z
|
|
|
107
107
|
pageSize: z.coerce.number().int().min(1).max(100).optional(),
|
|
108
108
|
sortField: z.string().optional(),
|
|
109
109
|
sortDir: z.enum(['asc', 'desc']).optional(),
|
|
110
|
+
search: z.string().optional(),
|
|
111
|
+
searchFields: z.string().optional(),
|
|
110
112
|
withDeleted: z.coerce.boolean().optional(),
|
|
111
113
|
format: z.enum(['csv', 'json', 'xml', 'markdown']).optional(),
|
|
112
114
|
exportScope: z.enum(['full']).optional(),
|
|
@@ -145,10 +147,15 @@ export async function GET(req: Request) {
|
|
|
145
147
|
const sortField = url.searchParams.get('sortField') || 'id'
|
|
146
148
|
const sortDir = (url.searchParams.get('sortDir') || 'asc').toLowerCase() === 'desc' ? 'desc' : 'asc'
|
|
147
149
|
const withDeleted = parseBooleanWithDefault(url.searchParams.get('withDeleted'), false)
|
|
150
|
+
const searchTerm = (url.searchParams.get('search') || '').trim()
|
|
151
|
+
const searchFields = (url.searchParams.get('searchFields') || '')
|
|
152
|
+
.split(',')
|
|
153
|
+
.map((field) => field.trim())
|
|
154
|
+
.filter(Boolean)
|
|
148
155
|
|
|
149
156
|
const qpEntries: Array<[string, string]> = []
|
|
150
157
|
for (const [key, val] of url.searchParams.entries()) {
|
|
151
|
-
if (['entityId','page','pageSize','sortField','sortDir','withDeleted','format','exportScope','export_scope','all','full'].includes(key)) continue
|
|
158
|
+
if (['entityId','page','pageSize','sortField','sortDir','withDeleted','format','exportScope','export_scope','all','full','search','searchFields'].includes(key)) continue
|
|
152
159
|
qpEntries.push([key, val])
|
|
153
160
|
}
|
|
154
161
|
|
|
@@ -246,6 +253,15 @@ export async function GET(req: Request) {
|
|
|
246
253
|
// even before the first record exists.
|
|
247
254
|
if (isCustomEntity) qopts.forceCustomEntityStorage = true
|
|
248
255
|
for (const [k, v] of qpEntries) buildFilter(k, v, isCustomEntity)
|
|
256
|
+
// Server-side full-result search: match the term against the requested fields
|
|
257
|
+
// (defaults to `id`) before pagination so totals/exports stay consistent with
|
|
258
|
+
// the active search instead of filtering only the current client page (#3229).
|
|
259
|
+
if (searchTerm) {
|
|
260
|
+
const fields = searchFields.length ? searchFields : ['id']
|
|
261
|
+
const pattern = `%${searchTerm}%`
|
|
262
|
+
const orClauses = fields.map((field) => ({ [field]: { $ilike: pattern } }))
|
|
263
|
+
;(filtersObj as any).$or = orClauses
|
|
264
|
+
}
|
|
249
265
|
const res = await qe.query(entityId as any, qopts)
|
|
250
266
|
const rawItems = res.items || []
|
|
251
267
|
const viewPageItems = rawItems.map(mapRow)
|
|
@@ -97,7 +97,16 @@ function RecordsPageInner({ params }: { params: { entityId?: string } }) {
|
|
|
97
97
|
keyExtras: [scopeVersion],
|
|
98
98
|
})
|
|
99
99
|
|
|
100
|
-
//
|
|
100
|
+
// Fields searched server-side: every visible column plus the base `id`.
|
|
101
|
+
const searchableFields = React.useMemo(() => {
|
|
102
|
+
const fields = (columns || [])
|
|
103
|
+
.map((col) => (col as any).accessorKey)
|
|
104
|
+
.filter((key): key is string => typeof key === 'string' && key.length > 0)
|
|
105
|
+
return Array.from(new Set(['id', ...fields]))
|
|
106
|
+
}, [columns])
|
|
107
|
+
|
|
108
|
+
// Fetch records whenever paging/sorting/filters/search change. Search is applied
|
|
109
|
+
// server-side (before pagination) so totals and exports stay consistent (#3229).
|
|
101
110
|
React.useEffect(() => {
|
|
102
111
|
let cancelled = false
|
|
103
112
|
const run = async () => {
|
|
@@ -107,6 +116,11 @@ function RecordsPageInner({ params }: { params: { entityId?: string } }) {
|
|
|
107
116
|
params.set('entityId', entityId)
|
|
108
117
|
params.set('page', String(page))
|
|
109
118
|
params.set('pageSize', String(pageSize))
|
|
119
|
+
const trimmedSearch = search.trim()
|
|
120
|
+
if (trimmedSearch) {
|
|
121
|
+
params.set('search', trimmedSearch)
|
|
122
|
+
if (searchableFields.length) params.set('searchFields', searchableFields.join(','))
|
|
123
|
+
}
|
|
110
124
|
const s = sorting?.[0]
|
|
111
125
|
if (s?.id) {
|
|
112
126
|
params.set('sortField', String(s.id))
|
|
@@ -154,7 +168,7 @@ function RecordsPageInner({ params }: { params: { entityId?: string } }) {
|
|
|
154
168
|
}
|
|
155
169
|
if (entityId) run()
|
|
156
170
|
return () => { cancelled = true }
|
|
157
|
-
}, [entityId, page, pageSize, sorting, filterValues, scopeVersion])
|
|
171
|
+
}, [entityId, page, pageSize, sorting, filterValues, scopeVersion, search, searchableFields])
|
|
158
172
|
|
|
159
173
|
// Build columns from custom field definitions only (no data round-trip)
|
|
160
174
|
React.useEffect(() => {
|
|
@@ -175,15 +189,9 @@ function RecordsPageInner({ params }: { params: { entityId?: string } }) {
|
|
|
175
189
|
setColumns(cols)
|
|
176
190
|
}, [cfDefs])
|
|
177
191
|
|
|
178
|
-
//
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const q = search.trim().toLowerCase()
|
|
182
|
-
return (rawData || []).filter((row: any) => {
|
|
183
|
-
const values = Object.values(row || {})
|
|
184
|
-
return values.some((v) => normalizeCell(v).toLowerCase().includes(q))
|
|
185
|
-
})
|
|
186
|
-
}, [rawData, search])
|
|
192
|
+
// Search is server-side (see fetch effect); render the fetched page as-is so
|
|
193
|
+
// pagination totals and exports stay consistent with the active search (#3229).
|
|
194
|
+
const data = rawData
|
|
187
195
|
|
|
188
196
|
const viewExportColumns = React.useMemo(() => {
|
|
189
197
|
return (columns || [])
|
|
@@ -213,8 +221,13 @@ function RecordsPageInner({ params }: { params: { entityId?: string } }) {
|
|
|
213
221
|
qp.set('sortField', String(sort.id))
|
|
214
222
|
qp.set('sortDir', sort.desc ? 'desc' : 'asc')
|
|
215
223
|
}
|
|
224
|
+
const trimmedSearch = search.trim()
|
|
225
|
+
if (trimmedSearch) {
|
|
226
|
+
qp.set('search', trimmedSearch)
|
|
227
|
+
if (searchableFields.length) qp.set('searchFields', searchableFields.join(','))
|
|
228
|
+
}
|
|
216
229
|
return `/api/entities/records?${qp.toString()}`
|
|
217
|
-
}, [entityId, sorting])
|
|
230
|
+
}, [entityId, sorting, search, searchableFields])
|
|
218
231
|
|
|
219
232
|
const exportConfig = React.useMemo(() => {
|
|
220
233
|
const safeEntityId = entityId.replace(/[^a-z0-9_-]/gi, '_') || 'records'
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
2
|
import { type Kysely, sql } from 'kysely'
|
|
3
|
+
import { runWithCacheTenant } from '@open-mercato/cache'
|
|
4
|
+
import {
|
|
5
|
+
buildCollectionTags,
|
|
6
|
+
isCrudCacheEnabled,
|
|
7
|
+
resolveCrudCache,
|
|
8
|
+
} from '@open-mercato/shared/lib/crud/cache'
|
|
3
9
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi/types'
|
|
4
10
|
import { resolveMessageContext } from '../../lib/routeHelpers'
|
|
5
11
|
import { unreadCountResponseSchema } from '../openapi'
|
|
@@ -8,6 +14,16 @@ export const metadata = {
|
|
|
8
14
|
GET: { requireAuth: true },
|
|
9
15
|
}
|
|
10
16
|
|
|
17
|
+
const UNREAD_COUNT_RESOURCE = 'messages.message'
|
|
18
|
+
const UNREAD_COUNT_TTL_MS = 10_000
|
|
19
|
+
|
|
20
|
+
function buildUnreadCountCacheKey(params: {
|
|
21
|
+
userId: string
|
|
22
|
+
orgId: string | null
|
|
23
|
+
}): string {
|
|
24
|
+
return `messages:unread-count:u=${params.userId}:org=${params.orgId ?? 'null'}`
|
|
25
|
+
}
|
|
26
|
+
|
|
11
27
|
function getDb(em: EntityManager): Kysely<any> {
|
|
12
28
|
return em.getKysely<any>()
|
|
13
29
|
}
|
|
@@ -17,6 +33,19 @@ export async function GET(req: Request) {
|
|
|
17
33
|
const em = ctx.container.resolve('em') as EntityManager
|
|
18
34
|
const db = getDb(em) as any
|
|
19
35
|
|
|
36
|
+
const orgId = scope.organizationId ?? null
|
|
37
|
+
const cache = isCrudCacheEnabled() ? resolveCrudCache(ctx.container) : null
|
|
38
|
+
const cacheKey = cache
|
|
39
|
+
? buildUnreadCountCacheKey({ userId: scope.userId, orgId })
|
|
40
|
+
: null
|
|
41
|
+
|
|
42
|
+
if (cache && cacheKey) {
|
|
43
|
+
const cached = await runWithCacheTenant(scope.tenantId, () => cache.get(cacheKey))
|
|
44
|
+
if (typeof cached === 'number') {
|
|
45
|
+
return Response.json({ unreadCount: cached })
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
20
49
|
let query = db
|
|
21
50
|
.selectFrom('message_recipients as r')
|
|
22
51
|
.innerJoin('messages as m', 'm.id', 'r.message_id')
|
|
@@ -38,6 +67,17 @@ export async function GET(req: Request) {
|
|
|
38
67
|
.executeTakeFirst() as { count: string | number } | undefined
|
|
39
68
|
const count = Number(row?.count ?? 0)
|
|
40
69
|
|
|
70
|
+
if (cache && cacheKey) {
|
|
71
|
+
try {
|
|
72
|
+
await runWithCacheTenant(scope.tenantId, () =>
|
|
73
|
+
cache.set(cacheKey, count, {
|
|
74
|
+
ttl: UNREAD_COUNT_TTL_MS,
|
|
75
|
+
tags: buildCollectionTags(UNREAD_COUNT_RESOURCE, scope.tenantId, [orgId]),
|
|
76
|
+
}),
|
|
77
|
+
)
|
|
78
|
+
} catch {}
|
|
79
|
+
}
|
|
80
|
+
|
|
41
81
|
return Response.json({ unreadCount: count })
|
|
42
82
|
}
|
|
43
83
|
|
|
@@ -11,6 +11,7 @@ import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/u
|
|
|
11
11
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
12
12
|
import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
|
|
13
13
|
import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
|
|
14
|
+
import { StatusBadge, type StatusBadgeVariant } from '@open-mercato/ui/primitives/status-badge'
|
|
14
15
|
|
|
15
16
|
const MUTATION_CONTEXT_ID = 'query_index.status.list:actions'
|
|
16
17
|
|
|
@@ -127,7 +128,7 @@ function createColumns(t: Translator): ColumnDef<Row>[] {
|
|
|
127
128
|
if (!record.vectorEnabled) return <span>—</span>
|
|
128
129
|
const ok = record.vectorCount != null && record.baseCount != null && record.vectorCount === record.baseCount
|
|
129
130
|
const display = formatCount(record.vectorCount)
|
|
130
|
-
const className = ok ? 'text-
|
|
131
|
+
const className = ok ? 'text-status-success-text' : 'text-status-warning-text'
|
|
131
132
|
return <span className={className}>{display}</span>
|
|
132
133
|
},
|
|
133
134
|
meta: { priority: 2 },
|
|
@@ -141,7 +142,7 @@ function createColumns(t: Translator): ColumnDef<Row>[] {
|
|
|
141
142
|
if (!record.fulltextEnabled) return <span>—</span>
|
|
142
143
|
const ok = record.fulltextCount != null && record.baseCount != null && record.fulltextCount === record.baseCount
|
|
143
144
|
const display = formatCount(record.fulltextCount)
|
|
144
|
-
const className = ok ? 'text-
|
|
145
|
+
const className = ok ? 'text-status-success-text' : 'text-status-warning-text'
|
|
145
146
|
return <span className={className}>{display}</span>
|
|
146
147
|
},
|
|
147
148
|
meta: { priority: 2 },
|
|
@@ -159,17 +160,14 @@ function createColumns(t: Translator): ColumnDef<Row>[] {
|
|
|
159
160
|
const label = jobProgress
|
|
160
161
|
? t('query_index.table.status.withProgress', { status: statusText, progress: jobProgress })
|
|
161
162
|
: statusText
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
: ok
|
|
171
|
-
? 'text-green-600'
|
|
172
|
-
: 'text-muted-foreground'
|
|
163
|
+
let variant: StatusBadgeVariant = 'neutral'
|
|
164
|
+
if (job) {
|
|
165
|
+
if (job.status === 'stalled') variant = 'error'
|
|
166
|
+
else if (job.status === 'reindexing' || job.status === 'purging') variant = 'warning'
|
|
167
|
+
else variant = ok ? 'success' : 'neutral'
|
|
168
|
+
} else {
|
|
169
|
+
variant = ok ? 'success' : 'neutral'
|
|
170
|
+
}
|
|
173
171
|
|
|
174
172
|
const lines: string[] = []
|
|
175
173
|
|
|
@@ -203,8 +201,12 @@ function createColumns(t: Translator): ColumnDef<Row>[] {
|
|
|
203
201
|
}
|
|
204
202
|
|
|
205
203
|
return (
|
|
206
|
-
<div className="space-y-1">
|
|
207
|
-
<
|
|
204
|
+
<div className="space-y-1.5">
|
|
205
|
+
<div>
|
|
206
|
+
<StatusBadge variant={variant} dot>
|
|
207
|
+
{label}
|
|
208
|
+
</StatusBadge>
|
|
209
|
+
</div>
|
|
208
210
|
{lines.length > 0 && (
|
|
209
211
|
<div className="text-xs text-muted-foreground">
|
|
210
212
|
{lines.map((line, idx) => (
|
|
@@ -70,5 +70,15 @@
|
|
|
70
70
|
"shipping_carriers.create.summary.to": "Nach",
|
|
71
71
|
"shipping_carriers.create.title": "Sendung beim Transporteur erstellen",
|
|
72
72
|
"shipping_carriers.feature.manage": "Versanddienstleister-Vorgaenge verwalten",
|
|
73
|
-
"shipping_carriers.feature.view": "Sendungen der Versanddienstleister anzeigen"
|
|
73
|
+
"shipping_carriers.feature.view": "Sendungen der Versanddienstleister anzeigen",
|
|
74
|
+
"shipping_carriers.status.cancelled": "Storniert",
|
|
75
|
+
"shipping_carriers.status.delivered": "Zugestellt",
|
|
76
|
+
"shipping_carriers.status.failed_delivery": "Zustellung fehlgeschlagen",
|
|
77
|
+
"shipping_carriers.status.in_transit": "Unterwegs",
|
|
78
|
+
"shipping_carriers.status.label_created": "Label erstellt",
|
|
79
|
+
"shipping_carriers.status.out_for_delivery": "In Zustellung",
|
|
80
|
+
"shipping_carriers.status.pending": "Ausstehend",
|
|
81
|
+
"shipping_carriers.status.picked_up": "Abgeholt",
|
|
82
|
+
"shipping_carriers.status.returned": "Zuruckgesendet",
|
|
83
|
+
"shipping_carriers.status.unknown": "Unbekannt"
|
|
74
84
|
}
|
|
@@ -70,5 +70,15 @@
|
|
|
70
70
|
"shipping_carriers.create.summary.to": "To",
|
|
71
71
|
"shipping_carriers.create.title": "Create carrier shipment",
|
|
72
72
|
"shipping_carriers.feature.manage": "Manage shipping carrier operations",
|
|
73
|
-
"shipping_carriers.feature.view": "View shipping carrier shipments"
|
|
73
|
+
"shipping_carriers.feature.view": "View shipping carrier shipments",
|
|
74
|
+
"shipping_carriers.status.cancelled": "Cancelled",
|
|
75
|
+
"shipping_carriers.status.delivered": "Delivered",
|
|
76
|
+
"shipping_carriers.status.failed_delivery": "Delivery failed",
|
|
77
|
+
"shipping_carriers.status.in_transit": "In transit",
|
|
78
|
+
"shipping_carriers.status.label_created": "Label created",
|
|
79
|
+
"shipping_carriers.status.out_for_delivery": "Out for delivery",
|
|
80
|
+
"shipping_carriers.status.pending": "Pending",
|
|
81
|
+
"shipping_carriers.status.picked_up": "Picked up",
|
|
82
|
+
"shipping_carriers.status.returned": "Returned",
|
|
83
|
+
"shipping_carriers.status.unknown": "Unknown"
|
|
74
84
|
}
|
|
@@ -4,4 +4,14 @@ export default {
|
|
|
4
4
|
'shipping_carriers.column.trackingStatus': 'Tracking status',
|
|
5
5
|
'shipping_carriers.column.shippingStatus': 'Shipping status',
|
|
6
6
|
'shipping_carriers.action.createShipment': 'Create shipment',
|
|
7
|
+
'shipping_carriers.status.cancelled': 'Cancelled',
|
|
8
|
+
'shipping_carriers.status.delivered': 'Delivered',
|
|
9
|
+
'shipping_carriers.status.failed_delivery': 'Delivery failed',
|
|
10
|
+
'shipping_carriers.status.in_transit': 'In transit',
|
|
11
|
+
'shipping_carriers.status.label_created': 'Label created',
|
|
12
|
+
'shipping_carriers.status.out_for_delivery': 'Out for delivery',
|
|
13
|
+
'shipping_carriers.status.pending': 'Pending',
|
|
14
|
+
'shipping_carriers.status.picked_up': 'Picked up',
|
|
15
|
+
'shipping_carriers.status.returned': 'Returned',
|
|
16
|
+
'shipping_carriers.status.unknown': 'Unknown',
|
|
7
17
|
}
|
|
@@ -70,5 +70,15 @@
|
|
|
70
70
|
"shipping_carriers.create.summary.to": "Hasta",
|
|
71
71
|
"shipping_carriers.create.title": "Crear envio con transportista",
|
|
72
72
|
"shipping_carriers.feature.manage": "Gestionar operaciones de transportistas",
|
|
73
|
-
"shipping_carriers.feature.view": "Ver envios de transportistas"
|
|
73
|
+
"shipping_carriers.feature.view": "Ver envios de transportistas",
|
|
74
|
+
"shipping_carriers.status.cancelled": "Cancelado",
|
|
75
|
+
"shipping_carriers.status.delivered": "Entregado",
|
|
76
|
+
"shipping_carriers.status.failed_delivery": "Entrega fallida",
|
|
77
|
+
"shipping_carriers.status.in_transit": "En transito",
|
|
78
|
+
"shipping_carriers.status.label_created": "Etiqueta creada",
|
|
79
|
+
"shipping_carriers.status.out_for_delivery": "En reparto",
|
|
80
|
+
"shipping_carriers.status.pending": "Pendiente",
|
|
81
|
+
"shipping_carriers.status.picked_up": "Recogido",
|
|
82
|
+
"shipping_carriers.status.returned": "Devuelto",
|
|
83
|
+
"shipping_carriers.status.unknown": "Desconocido"
|
|
74
84
|
}
|
|
@@ -70,5 +70,15 @@
|
|
|
70
70
|
"shipping_carriers.create.summary.to": "Odbiór",
|
|
71
71
|
"shipping_carriers.create.title": "Utwórz przesyłkę u przewoźnika",
|
|
72
72
|
"shipping_carriers.feature.manage": "Zarzadzanie operacjami przewoznikow",
|
|
73
|
-
"shipping_carriers.feature.view": "Podglad przesylek przewoznikow"
|
|
73
|
+
"shipping_carriers.feature.view": "Podglad przesylek przewoznikow",
|
|
74
|
+
"shipping_carriers.status.cancelled": "Anulowano",
|
|
75
|
+
"shipping_carriers.status.delivered": "Doreczono",
|
|
76
|
+
"shipping_carriers.status.failed_delivery": "Doreczenie nieudane",
|
|
77
|
+
"shipping_carriers.status.in_transit": "W transporcie",
|
|
78
|
+
"shipping_carriers.status.label_created": "Etykieta utworzona",
|
|
79
|
+
"shipping_carriers.status.out_for_delivery": "W doreczeniu",
|
|
80
|
+
"shipping_carriers.status.pending": "Oczekuje",
|
|
81
|
+
"shipping_carriers.status.picked_up": "Odebrano",
|
|
82
|
+
"shipping_carriers.status.returned": "Zwrocono",
|
|
83
|
+
"shipping_carriers.status.unknown": "Nieznany"
|
|
74
84
|
}
|
|
@@ -4,4 +4,14 @@ export default {
|
|
|
4
4
|
'shipping_carriers.column.trackingStatus': 'Status śledzenia',
|
|
5
5
|
'shipping_carriers.column.shippingStatus': 'Status wysyłki',
|
|
6
6
|
'shipping_carriers.action.createShipment': 'Utwórz przesyłkę',
|
|
7
|
+
'shipping_carriers.status.cancelled': 'Anulowano',
|
|
8
|
+
'shipping_carriers.status.delivered': 'Doręczono',
|
|
9
|
+
'shipping_carriers.status.failed_delivery': 'Doręczenie nieudane',
|
|
10
|
+
'shipping_carriers.status.in_transit': 'W transporcie',
|
|
11
|
+
'shipping_carriers.status.label_created': 'Etykieta utworzona',
|
|
12
|
+
'shipping_carriers.status.out_for_delivery': 'W doręczeniu',
|
|
13
|
+
'shipping_carriers.status.pending': 'Oczekuje',
|
|
14
|
+
'shipping_carriers.status.picked_up': 'Odebrano',
|
|
15
|
+
'shipping_carriers.status.returned': 'Zwrócono',
|
|
16
|
+
'shipping_carriers.status.unknown': 'Nieznany',
|
|
7
17
|
}
|
package/src/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.client.tsx
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
4
|
+
import { StatusBadge, type StatusMap } from '@open-mercato/ui/primitives/status-badge'
|
|
5
|
+
import type { UnifiedShipmentStatus } from '../../../lib/adapter'
|
|
6
|
+
|
|
7
|
+
type ShippingStatus = UnifiedShipmentStatus | 'pending'
|
|
8
|
+
|
|
9
|
+
const shippingStatusVariantMap: StatusMap<ShippingStatus> = {
|
|
10
|
+
pending: 'neutral',
|
|
11
|
+
label_created: 'info',
|
|
12
|
+
picked_up: 'info',
|
|
13
|
+
in_transit: 'info',
|
|
14
|
+
out_for_delivery: 'info',
|
|
15
|
+
delivered: 'success',
|
|
16
|
+
failed_delivery: 'error',
|
|
17
|
+
returned: 'warning',
|
|
18
|
+
cancelled: 'error',
|
|
19
|
+
unknown: 'neutral',
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type ShippingStatusBadgeWidgetProps = {
|
|
23
|
+
status: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default function ShippingStatusBadgeWidget({ status }: ShippingStatusBadgeWidgetProps) {
|
|
27
|
+
const t = useT()
|
|
28
|
+
const variant = shippingStatusVariantMap[status as ShippingStatus] ?? 'neutral'
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<StatusBadge variant={variant} dot>
|
|
32
|
+
{t(`shipping_carriers.status.${status}`, status)}
|
|
33
|
+
</StatusBadge>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
@@ -1,20 +1,6 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
1
2
|
import type { InjectionColumnWidget } from '@open-mercato/shared/modules/widgets/injection'
|
|
2
|
-
|
|
3
|
-
const SHIPPING_STATUS_COLORS: Record<string, string> = {
|
|
4
|
-
pending: 'bg-gray-100 text-gray-700',
|
|
5
|
-
label_created: 'bg-slate-100 text-slate-700',
|
|
6
|
-
picked_up: 'bg-sky-100 text-sky-700',
|
|
7
|
-
in_transit: 'bg-blue-100 text-blue-700',
|
|
8
|
-
out_for_delivery: 'bg-indigo-100 text-indigo-700',
|
|
9
|
-
delivered: 'bg-green-100 text-green-700',
|
|
10
|
-
returned: 'bg-amber-100 text-amber-700',
|
|
11
|
-
cancelled: 'bg-red-100 text-red-700',
|
|
12
|
-
failed_delivery: 'bg-orange-100 text-orange-700',
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function formatShippingStatusLabel(value: string): string {
|
|
16
|
-
return value.replace(/_/g, ' ')
|
|
17
|
-
}
|
|
3
|
+
import ShippingStatusBadgeWidget from './widget.client'
|
|
18
4
|
|
|
19
5
|
const widget: InjectionColumnWidget = {
|
|
20
6
|
metadata: {
|
|
@@ -30,12 +16,7 @@ const widget: InjectionColumnWidget = {
|
|
|
30
16
|
cell: ({ getValue }) => {
|
|
31
17
|
const value = getValue()
|
|
32
18
|
if (typeof value !== 'string' || value.length === 0) return null
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<span className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${colors}`}>
|
|
36
|
-
{formatShippingStatusLabel(value)}
|
|
37
|
-
</span>
|
|
38
|
-
)
|
|
19
|
+
return React.createElement(ShippingStatusBadgeWidget, { status: value })
|
|
39
20
|
},
|
|
40
21
|
},
|
|
41
22
|
],
|
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import { useParams } from 'next/navigation'
|
|
5
5
|
import Link from 'next/link'
|
|
6
|
+
import { useQuery } from '@tanstack/react-query'
|
|
6
7
|
import { ExternalLink, Languages } from 'lucide-react'
|
|
7
8
|
import type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'
|
|
8
9
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
10
|
+
import { hasAllFeatures } from '@open-mercato/shared/lib/auth/featureMatch'
|
|
11
|
+
import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
|
|
9
12
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
10
13
|
import {
|
|
11
14
|
Drawer,
|
|
@@ -21,20 +24,34 @@ import { extractRecordId } from '../../../lib/extract-record-id'
|
|
|
21
24
|
type WidgetContext = { entityId?: string; recordId?: string }
|
|
22
25
|
type WidgetData = Record<string, unknown> & { id?: string | number }
|
|
23
26
|
|
|
27
|
+
type FeatureCheckResponse = {
|
|
28
|
+
ok: boolean
|
|
29
|
+
granted: string[]
|
|
30
|
+
userId: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const TRANSLATION_ACCESS_FEATURES = ['translations.view']
|
|
34
|
+
|
|
35
|
+
// Probe access through the shared apiCall/React Query path so multiple injected
|
|
36
|
+
// widget instances dedupe a single request via the query key. feature-check
|
|
37
|
+
// always answers 200 with the granted feature list, so a user without
|
|
38
|
+
// translations.view is hidden gracefully — no login redirect, no forbidden flash.
|
|
24
39
|
function useTranslationAccess(): boolean {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
const { data } = useQuery<boolean>({
|
|
41
|
+
queryKey: ['translations', 'widget-access', ...TRANSLATION_ACCESS_FEATURES],
|
|
42
|
+
queryFn: async () => {
|
|
43
|
+
const res = await apiCall<FeatureCheckResponse>('/api/auth/feature-check', {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: { 'content-type': 'application/json' },
|
|
46
|
+
body: JSON.stringify({ features: TRANSLATION_ACCESS_FEATURES }),
|
|
47
|
+
})
|
|
48
|
+
if (!res.ok) return false
|
|
49
|
+
return hasAllFeatures(TRANSLATION_ACCESS_FEATURES, res.result?.granted ?? [])
|
|
50
|
+
},
|
|
51
|
+
staleTime: 5 * 60 * 1000,
|
|
52
|
+
retry: false,
|
|
53
|
+
})
|
|
54
|
+
return data === true
|
|
38
55
|
}
|
|
39
56
|
|
|
40
57
|
export default function TranslationWidget({ context, data }: InjectionWidgetComponentProps<WidgetContext, WidgetData>) {
|