@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.
Files changed (120) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/helpers/integration/authFixtures.js.map +1 -1
  3. package/dist/helpers/integration/authUi.js +10 -2
  4. package/dist/helpers/integration/authUi.js.map +2 -2
  5. package/dist/helpers/integration/salesUi.js +3 -1
  6. package/dist/helpers/integration/salesUi.js.map +2 -2
  7. package/dist/modules/api_docs/frontend/docs/api/Explorer.js +10 -1
  8. package/dist/modules/api_docs/frontend/docs/api/Explorer.js.map +2 -2
  9. package/dist/modules/audit_logs/lib/display-helpers.js +28 -27
  10. package/dist/modules/audit_logs/lib/display-helpers.js.map +2 -2
  11. package/dist/modules/auth/api/users/route.js +21 -3
  12. package/dist/modules/auth/api/users/route.js.map +2 -2
  13. package/dist/modules/auth/backend/users/[id]/edit/page.js +6 -1
  14. package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
  15. package/dist/modules/auth/backend/users/create/page.js +4 -1
  16. package/dist/modules/auth/backend/users/create/page.js.map +2 -2
  17. package/dist/modules/auth/backend/users/page.js +56 -2
  18. package/dist/modules/auth/backend/users/page.js.map +3 -3
  19. package/dist/modules/auth/commands/users.js +14 -0
  20. package/dist/modules/auth/commands/users.js.map +2 -2
  21. package/dist/modules/auth/data/validators.js +8 -0
  22. package/dist/modules/auth/data/validators.js.map +2 -2
  23. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +1 -1
  24. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
  25. package/dist/modules/customers/backend/customers/deals/[id]/page.js +15 -9
  26. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  27. package/dist/modules/customers/backend/customers/deals/pipeline/page.js +26 -3
  28. package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
  29. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +1 -1
  30. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  31. package/dist/modules/customers/components/PipelineSettings.js +108 -99
  32. package/dist/modules/customers/components/PipelineSettings.js.map +2 -2
  33. package/dist/modules/customers/components/detail/AssignRoleDialog.js +13 -5
  34. package/dist/modules/customers/components/detail/AssignRoleDialog.js.map +2 -2
  35. package/dist/modules/customers/components/detail/EntityTagsDialog.js +10 -2
  36. package/dist/modules/customers/components/detail/EntityTagsDialog.js.map +2 -2
  37. package/dist/modules/customers/components/detail/PersonCompaniesSection.js +15 -8
  38. package/dist/modules/customers/components/detail/PersonCompaniesSection.js.map +2 -2
  39. package/dist/modules/customers/components/detail/RolesSection.js +35 -22
  40. package/dist/modules/customers/components/detail/RolesSection.js.map +2 -2
  41. package/dist/modules/customers/components/detail/TasksSection.js +2 -2
  42. package/dist/modules/customers/components/detail/TasksSection.js.map +2 -2
  43. package/dist/modules/customers/components/detail/schedule/DateTimeFields.js +73 -64
  44. package/dist/modules/customers/components/detail/schedule/DateTimeFields.js.map +2 -2
  45. package/dist/modules/customers/components/linking/LinkEntityDialog.js +11 -2
  46. package/dist/modules/customers/components/linking/LinkEntityDialog.js.map +2 -2
  47. package/dist/modules/data_sync/backend/data-sync/page.js +2 -9
  48. package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
  49. package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js +14 -29
  50. package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js.map +2 -2
  51. package/dist/modules/dictionaries/components/DictionariesManager.js +10 -2
  52. package/dist/modules/dictionaries/components/DictionariesManager.js.map +2 -2
  53. package/dist/modules/integrations/backend/integrations/[id]/page.js +79 -95
  54. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
  55. package/dist/modules/payment_gateways/backend/payment-gateways/page.js +49 -70
  56. package/dist/modules/payment_gateways/backend/payment-gateways/page.js.map +2 -2
  57. package/dist/modules/planner/components/AvailabilityRuleSetForm.js +1 -1
  58. package/dist/modules/planner/components/AvailabilityRuleSetForm.js.map +1 -1
  59. package/dist/modules/planner/components/AvailabilityRulesEditor.js +19 -19
  60. package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
  61. package/dist/modules/resources/components/ResourceCrudForm.js +1 -1
  62. package/dist/modules/resources/components/ResourceCrudForm.js.map +1 -1
  63. package/dist/modules/resources/components/ResourceTypeCrudForm.js +1 -1
  64. package/dist/modules/resources/components/ResourceTypeCrudForm.js.map +2 -2
  65. package/dist/modules/sales/components/documents/ReturnDialog.js +11 -8
  66. package/dist/modules/sales/components/documents/ReturnDialog.js.map +2 -2
  67. package/dist/modules/staff/backend/staff/profile/create/page.js +1 -1
  68. package/dist/modules/staff/backend/staff/profile/create/page.js.map +2 -2
  69. package/dist/modules/staff/components/TeamForm.js +1 -1
  70. package/dist/modules/staff/components/TeamForm.js.map +2 -2
  71. package/dist/modules/staff/components/TeamMemberForm.js +1 -1
  72. package/dist/modules/staff/components/TeamMemberForm.js.map +1 -1
  73. package/dist/modules/staff/components/TeamRoleForm.js +1 -1
  74. package/dist/modules/staff/components/TeamRoleForm.js.map +2 -2
  75. package/package.json +5 -5
  76. package/src/helpers/integration/authFixtures.ts +1 -1
  77. package/src/helpers/integration/authUi.ts +11 -2
  78. package/src/helpers/integration/salesUi.ts +10 -1
  79. package/src/modules/api_docs/frontend/docs/api/Explorer.tsx +7 -3
  80. package/src/modules/audit_logs/lib/display-helpers.tsx +22 -14
  81. package/src/modules/auth/api/users/route.ts +22 -3
  82. package/src/modules/auth/backend/users/[id]/edit/page.tsx +9 -1
  83. package/src/modules/auth/backend/users/create/page.tsx +6 -2
  84. package/src/modules/auth/backend/users/page.tsx +60 -3
  85. package/src/modules/auth/commands/users.ts +15 -0
  86. package/src/modules/auth/data/validators.ts +8 -0
  87. package/src/modules/auth/i18n/de.json +4 -0
  88. package/src/modules/auth/i18n/en.json +4 -0
  89. package/src/modules/auth/i18n/es.json +4 -0
  90. package/src/modules/auth/i18n/pl.json +4 -0
  91. package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +1 -1
  92. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +12 -13
  93. package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +17 -13
  94. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +1 -1
  95. package/src/modules/customers/components/PipelineSettings.tsx +95 -95
  96. package/src/modules/customers/components/detail/AssignRoleDialog.tsx +7 -4
  97. package/src/modules/customers/components/detail/EntityTagsDialog.tsx +13 -8
  98. package/src/modules/customers/components/detail/PersonCompaniesSection.tsx +16 -12
  99. package/src/modules/customers/components/detail/RolesSection.tsx +34 -31
  100. package/src/modules/customers/components/detail/TasksSection.tsx +16 -8
  101. package/src/modules/customers/components/detail/schedule/DateTimeFields.tsx +77 -51
  102. package/src/modules/customers/components/linking/LinkEntityDialog.tsx +7 -3
  103. package/src/modules/data_sync/backend/data-sync/page.tsx +4 -7
  104. package/src/modules/data_sync/backend/data-sync/runs/[id]/page.tsx +19 -54
  105. package/src/modules/dictionaries/components/DictionariesManager.tsx +7 -4
  106. package/src/modules/integrations/backend/integrations/[id]/page.tsx +101 -146
  107. package/src/modules/payment_gateways/backend/payment-gateways/page.tsx +60 -103
  108. package/src/modules/planner/components/AvailabilityRuleSetForm.tsx +1 -1
  109. package/src/modules/planner/components/AvailabilityRulesEditor.tsx +17 -17
  110. package/src/modules/resources/components/ResourceCrudForm.tsx +1 -1
  111. package/src/modules/resources/components/ResourceTypeCrudForm.tsx +1 -1
  112. package/src/modules/sales/components/documents/ReturnDialog.tsx +10 -7
  113. package/src/modules/sales/i18n/de.json +2 -0
  114. package/src/modules/sales/i18n/en.json +2 -0
  115. package/src/modules/sales/i18n/es.json +2 -0
  116. package/src/modules/sales/i18n/pl.json +2 -0
  117. package/src/modules/staff/backend/staff/profile/create/page.tsx +1 -1
  118. package/src/modules/staff/components/TeamForm.tsx +1 -1
  119. package/src/modules/staff/components/TeamMemberForm.tsx +1 -1
  120. package/src/modules/staff/components/TeamRoleForm.tsx +1 -1
@@ -35,7 +35,9 @@ import {
35
35
  type IntegrationDetailBuiltInTab,
36
36
  } from '@open-mercato/shared/modules/integrations/types'
37
37
  import { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'
38
- import { Activity, AlertTriangle, Bell, Calendar, CheckCircle2, ChevronDown, ChevronRight, CreditCard, FileText, HardDrive, Key, MessageSquare, RefreshCw, Settings, Truck, Webhook, XCircle, Zap } from 'lucide-react'
38
+ import { LogList, type LogListEntry } from '@open-mercato/ui/backend/LogList'
39
+ import { Activity, AlertTriangle, Bell, Calendar, CheckCircle2, CreditCard, FileText, FileX, HardDrive, Key, MessageSquare, RefreshCw, Settings, Truck, Webhook, XCircle, Zap } from 'lucide-react'
40
+ import { EmptyState } from '@open-mercato/ui/primitives/empty-state'
39
41
  import { IntegrationScheduleTab } from '../../../../data_sync/components/IntegrationScheduleTab'
40
42
  import {
41
43
  buildIntegrationDetailInjectedTabs,
@@ -129,12 +131,6 @@ type HealthCheckResponse = {
129
131
  latencyMs: number | null
130
132
  }
131
133
 
132
- const LOG_LEVEL_STYLES: Record<string, string> = {
133
- info: 'bg-blue-100 text-blue-800',
134
- warn: 'bg-yellow-100 text-yellow-800',
135
- error: 'bg-red-100 text-red-800',
136
- }
137
-
138
134
  const HEALTH_STATUS_STYLES: Record<string, string> = {
139
135
  healthy: 'bg-green-100 text-green-800',
140
136
  degraded: 'bg-yellow-100 text-yellow-800',
@@ -337,7 +333,6 @@ export default function IntegrationDetailPage({ params }: IntegrationDetailPageP
337
333
  const [logs, setLogs] = React.useState<LogEntry[]>([])
338
334
  const [logLevel, setLogLevel] = React.useState<string>('')
339
335
  const [isLoadingLogs, setIsLoadingLogs] = React.useState(false)
340
- const [expandedLogId, setExpandedLogId] = React.useState<string | null>(null)
341
336
 
342
337
  const [isCheckingHealth, setIsCheckingHealth] = React.useState(false)
343
338
  const [isTogglingState, setIsTogglingState] = React.useState(false)
@@ -522,9 +517,6 @@ export default function IntegrationDetailPage({ params }: IntegrationDetailPageP
522
517
  React.useEffect(() => { void loadDetail() }, [loadDetail])
523
518
  React.useEffect(() => { void loadCredentials() }, [loadCredentials])
524
519
  React.useEffect(() => { void loadLogs() }, [loadLogs])
525
- React.useEffect(() => {
526
- setExpandedLogId((current) => (current && logs.some((log) => log.id === current) ? current : null))
527
- }, [logs])
528
520
 
529
521
  const handleToggleState = React.useCallback(async (enabled: boolean) => {
530
522
  const currentIntegrationId = resolveCurrentIntegrationId()
@@ -1210,144 +1202,107 @@ export default function IntegrationDetailPage({ params }: IntegrationDetailPageP
1210
1202
  </div>
1211
1203
  {isLoadingLogs ? (
1212
1204
  <div className="flex justify-center py-8"><Spinner /></div>
1213
- ) : logs.length === 0 ? (
1214
- <p className="py-4 text-sm text-muted-foreground">{t('integrations.detail.logs.empty')}</p>
1215
1205
  ) : (
1216
- <div className="rounded-lg border">
1217
- <table className="w-full text-sm">
1218
- <thead>
1219
- <tr className="border-b bg-muted/50">
1220
- <th className="px-4 py-2 text-left font-medium">{t('integrations.detail.logs.columns.time')}</th>
1221
- <th className="px-4 py-2 text-left font-medium">{t('integrations.detail.logs.columns.level')}</th>
1222
- <th className="px-4 py-2 text-left font-medium">{t('integrations.detail.logs.columns.message')}</th>
1223
- </tr>
1224
- </thead>
1225
- <tbody>
1226
- {logs.map((log) => {
1227
- const isExpanded = expandedLogId === log.id
1228
- const metadataEntries = [
1229
- ['Time', new Date(log.createdAt).toLocaleString()],
1230
- ['Level', log.level],
1231
- ['Code', log.code ?? null],
1232
- ['Run ID', log.runId ?? null],
1233
- ['Entity Type', log.scopeEntityType ?? null],
1234
- ['Entity ID', log.scopeEntityId ?? null],
1235
- ].filter((entry): entry is [string, string] => typeof entry[1] === 'string' && entry[1].trim().length > 0)
1236
- const { inlineEntries, nestedEntries } = splitLogPayload(log.payload)
1237
-
1238
- return (
1239
- <React.Fragment key={log.id}>
1240
- <tr className="border-b last:border-0">
1241
- <td className="whitespace-nowrap px-4 py-2 text-muted-foreground">
1242
- {new Date(log.createdAt).toLocaleString()}
1243
- </td>
1244
- <td className="px-4 py-2">
1245
- <Badge variant="secondary" className={LOG_LEVEL_STYLES[log.level] ?? ''}>
1246
- {log.level}
1247
- </Badge>
1248
- </td>
1249
- <td className="px-4 py-2">
1250
- <Button
1251
- type="button"
1252
- variant="ghost"
1253
- size="sm"
1254
- className="h-auto w-full justify-start gap-2 px-0 py-0 text-left hover:bg-transparent"
1255
- onClick={() => setExpandedLogId((current) => (current === log.id ? null : log.id))}
1256
- >
1257
- {isExpanded ? <ChevronDown className="h-4 w-4 shrink-0" /> : <ChevronRight className="h-4 w-4 shrink-0" />}
1258
- <span className="truncate">{log.message}</span>
1259
- </Button>
1260
- </td>
1261
- </tr>
1262
- {isExpanded ? (
1263
- <tr className="border-b bg-muted/30 last:border-0">
1264
- <td colSpan={3} className="px-4 py-4">
1265
- <div className="space-y-4 rounded-lg border bg-card p-4">
1266
- <div className="grid gap-4 lg:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)]">
1267
- <div className="space-y-4">
1268
- <section className="space-y-3">
1269
- <div>
1270
- <p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
1271
- {t('integrations.detail.logs.details.summary', 'Summary')}
1272
- </p>
1273
- <p className="mt-1 text-sm font-medium">{log.message}</p>
1274
- </div>
1275
- {metadataEntries.length > 0 ? (
1276
- <dl className="grid gap-3 sm:grid-cols-2">
1277
- {metadataEntries.map(([label, value]) => (
1278
- <div key={label} className="rounded-md border bg-muted/30 px-3 py-2">
1279
- <dt className="text-overline font-medium uppercase tracking-wide text-muted-foreground">
1280
- {label}
1281
- </dt>
1282
- <dd className="mt-1 break-all text-sm">{value}</dd>
1283
- </div>
1284
- ))}
1285
- </dl>
1286
- ) : null}
1287
- </section>
1288
-
1289
- {inlineEntries.length > 0 ? (
1290
- <section className="space-y-3">
1291
- <p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
1292
- {t('integrations.detail.logs.details.fields', 'Fields')}
1293
- </p>
1294
- <dl className="grid gap-3 sm:grid-cols-2">
1295
- {inlineEntries.map(([key, value]) => (
1296
- <div key={key} className="rounded-md border bg-muted/30 px-3 py-2">
1297
- <dt className="text-overline font-medium uppercase tracking-wide text-muted-foreground">
1298
- {formatLogDetailLabel(key)}
1299
- </dt>
1300
- <dd className="mt-1 break-words text-sm">
1301
- {formatLogPrimitiveValue(value)}
1302
- </dd>
1303
- </div>
1304
- ))}
1305
- </dl>
1306
- </section>
1307
- ) : null}
1308
- </div>
1309
-
1310
- <div className="space-y-3">
1311
- {nestedEntries.map(([key, value]) => (
1312
- <JsonDisplay
1313
- key={key}
1314
- data={value}
1315
- title={formatLogDetailLabel(key)}
1316
- defaultExpanded
1317
- maxInitialDepth={1}
1318
- theme="dark"
1319
- maxHeight="16rem"
1320
- className="p-4"
1321
- />
1322
- ))}
1323
- {log.payload && nestedEntries.length === 0 ? (
1324
- <JsonDisplay
1325
- data={log.payload}
1326
- title={t('integrations.detail.logs.details.payload', 'Payload')}
1327
- defaultExpanded
1328
- maxInitialDepth={1}
1329
- theme="dark"
1330
- maxHeight="16rem"
1331
- className="p-4"
1332
- />
1333
- ) : null}
1334
- {!log.payload ? (
1335
- <div className="rounded-lg border border-dashed px-4 py-6 text-sm text-muted-foreground">
1336
- {t('integrations.detail.logs.details.noPayload', 'No structured payload was stored for this log entry.')}
1337
- </div>
1338
- ) : null}
1339
- </div>
1206
+ <LogList
1207
+ entries={logs.map<LogListEntry>((log) => {
1208
+ const metadataEntries = [
1209
+ ['Time', new Date(log.createdAt).toLocaleString()],
1210
+ ['Level', log.level],
1211
+ ['Code', log.code ?? null],
1212
+ ['Run ID', log.runId ?? null],
1213
+ ['Entity Type', log.scopeEntityType ?? null],
1214
+ ['Entity ID', log.scopeEntityId ?? null],
1215
+ ].filter((entry): entry is [string, string] => typeof entry[1] === 'string' && entry[1].trim().length > 0)
1216
+ const { inlineEntries, nestedEntries } = splitLogPayload(log.payload)
1217
+
1218
+ return {
1219
+ id: log.id,
1220
+ time: new Date(log.createdAt).toLocaleString(),
1221
+ level: log.level,
1222
+ message: log.message,
1223
+ body: (
1224
+ <div className="grid gap-4 lg:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)]">
1225
+ <div className="space-y-4">
1226
+ <section className="space-y-3">
1227
+ <div>
1228
+ <p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
1229
+ {t('integrations.detail.logs.details.summary', 'Summary')}
1230
+ </p>
1231
+ <p className="mt-1 text-sm font-medium">{log.message}</p>
1232
+ </div>
1233
+ {metadataEntries.length > 0 ? (
1234
+ <dl className="grid gap-3 sm:grid-cols-2">
1235
+ {metadataEntries.map(([label, value]) => (
1236
+ <div key={label} className="rounded-md border bg-muted/30 px-3 py-2">
1237
+ <dt className="text-overline font-medium uppercase tracking-wide text-muted-foreground">
1238
+ {label}
1239
+ </dt>
1240
+ <dd className="mt-1 break-all text-sm">{value}</dd>
1241
+ </div>
1242
+ ))}
1243
+ </dl>
1244
+ ) : null}
1245
+ </section>
1246
+
1247
+ {inlineEntries.length > 0 ? (
1248
+ <section className="space-y-3">
1249
+ <p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
1250
+ {t('integrations.detail.logs.details.fields', 'Fields')}
1251
+ </p>
1252
+ <dl className="grid gap-3 sm:grid-cols-2">
1253
+ {inlineEntries.map(([key, value]) => (
1254
+ <div key={key} className="rounded-md border bg-muted/30 px-3 py-2">
1255
+ <dt className="text-overline font-medium uppercase tracking-wide text-muted-foreground">
1256
+ {formatLogDetailLabel(key)}
1257
+ </dt>
1258
+ <dd className="mt-1 break-words text-sm">
1259
+ {formatLogPrimitiveValue(value)}
1260
+ </dd>
1340
1261
  </div>
1341
- </div>
1342
- </td>
1343
- </tr>
1262
+ ))}
1263
+ </dl>
1264
+ </section>
1344
1265
  ) : null}
1345
- </React.Fragment>
1346
- )
1347
- })}
1348
- </tbody>
1349
- </table>
1350
- </div>
1266
+ </div>
1267
+
1268
+ <div className="space-y-3">
1269
+ {nestedEntries.map(([key, value]) => (
1270
+ <JsonDisplay
1271
+ key={key}
1272
+ data={value}
1273
+ title={formatLogDetailLabel(key)}
1274
+ defaultExpanded
1275
+ maxInitialDepth={1}
1276
+ theme="dark"
1277
+ maxHeight="16rem"
1278
+ className="p-4"
1279
+ />
1280
+ ))}
1281
+ {log.payload && nestedEntries.length === 0 ? (
1282
+ <JsonDisplay
1283
+ data={log.payload}
1284
+ title={t('integrations.detail.logs.details.payload', 'Payload')}
1285
+ defaultExpanded
1286
+ maxInitialDepth={1}
1287
+ theme="dark"
1288
+ maxHeight="16rem"
1289
+ className="p-4"
1290
+ />
1291
+ ) : null}
1292
+ {!log.payload ? (
1293
+ <EmptyState
1294
+ size="sm"
1295
+ icon={<FileX className="h-8 w-8" aria-hidden="true" />}
1296
+ title={t('integrations.detail.logs.details.noPayload', 'No structured payload was stored for this log entry.')}
1297
+ />
1298
+ ) : null}
1299
+ </div>
1300
+ </div>
1301
+ ),
1302
+ }
1303
+ })}
1304
+ emptyMessage={t('integrations.detail.logs.empty')}
1305
+ />
1351
1306
  )}
1352
1307
  </TabsContent>
1353
1308
  ) : null}
@@ -18,7 +18,8 @@ import { Badge } from '@open-mercato/ui/primitives/badge'
18
18
  import { Button } from '@open-mercato/ui/primitives/button'
19
19
  import { Card, CardContent, CardHeader, CardTitle } from '@open-mercato/ui/primitives/card'
20
20
  import { Spinner } from '@open-mercato/ui/primitives/spinner'
21
- import { ChevronDown, ChevronRight, CreditCard, HandCoins, RefreshCw, Webhook } from 'lucide-react'
21
+ import { LogList, type LogListEntry } from '@open-mercato/ui/backend/LogList'
22
+ import { CreditCard, HandCoins, RefreshCw, Webhook } from 'lucide-react'
22
23
 
23
24
  type TransactionRow = {
24
25
  id: string
@@ -102,12 +103,6 @@ const STATUS_STYLES: Record<string, string> = {
102
103
  unknown: 'bg-purple-100 text-purple-800',
103
104
  }
104
105
 
105
- const LOG_LEVEL_STYLES: Record<string, string> = {
106
- info: 'bg-blue-100 text-blue-800',
107
- warn: 'bg-yellow-100 text-yellow-800',
108
- error: 'bg-red-100 text-red-800',
109
- }
110
-
111
106
  function formatDateTime(value: string | null | undefined): string {
112
107
  if (!value) return '—'
113
108
  const parsed = new Date(value)
@@ -194,7 +189,6 @@ export default function PaymentTransactionsPage() {
194
189
  const [detail, setDetail] = React.useState<TransactionDetail | null>(null)
195
190
  const [isLoadingDetail, setIsLoadingDetail] = React.useState(false)
196
191
  const [detailError, setDetailError] = React.useState<string | null>(null)
197
- const [expandedLogId, setExpandedLogId] = React.useState<string | null>(null)
198
192
  const [isRefreshingStatus, setIsRefreshingStatus] = React.useState(false)
199
193
  const [isCapturing, setIsCapturing] = React.useState(false)
200
194
  const { runMutation } = useGuardedMutation<{
@@ -249,7 +243,6 @@ export default function PaymentTransactionsPage() {
249
243
  const call = await apiCall<TransactionDetail>(`/api/payment_gateways/transactions/${encodeURIComponent(transactionId)}`, undefined, { fallback: null })
250
244
  if (call.ok && call.result) {
251
245
  setDetail(call.result)
252
- setExpandedLogId((current) => (current && call.result?.logs.some((log) => log.id === current) ? current : null))
253
246
  } else {
254
247
  setDetail(null)
255
248
  setDetailError(t('payment_gateways.transactions.error.loadDetail', 'Failed to load transaction details'))
@@ -610,100 +603,64 @@ export default function PaymentTransactionsPage() {
610
603
 
611
604
  <section className="space-y-3">
612
605
  <h3 className="text-sm font-semibold">{t('payment_gateways.transactions.detail.logs', 'Gateway logs')}</h3>
613
- {detail.logs.length === 0 ? (
614
- <p className="text-sm text-muted-foreground">{t('payment_gateways.transactions.detail.logsEmpty', 'No transaction-scoped logs are available yet.')}</p>
615
- ) : (
616
- <div className="overflow-hidden rounded-lg border">
617
- <table className="w-full text-sm">
618
- <thead>
619
- <tr className="border-b bg-muted/50">
620
- <th className="px-4 py-2 text-left font-medium">{t('payment_gateways.transactions.columns.time', 'Time')}</th>
621
- <th className="px-4 py-2 text-left font-medium">{t('payment_gateways.transactions.columns.level', 'Level')}</th>
622
- <th className="px-4 py-2 text-left font-medium">{t('payment_gateways.transactions.columns.message', 'Message')}</th>
623
- </tr>
624
- </thead>
625
- <tbody>
626
- {detail.logs.map((log) => {
627
- const isExpanded = expandedLogId === log.id
628
- const metadataEntries = [
629
- [t('payment_gateways.transactions.log.time', 'Time'), formatDateTime(log.createdAt)],
630
- [t('payment_gateways.transactions.log.level', 'Level'), t(`payment_gateways.transactions.level.${log.level}`, log.level)],
631
- [t('payment_gateways.transactions.log.code', 'Code'), log.code ?? null],
632
- [t('payment_gateways.transactions.log.runId', 'Run ID'), log.runId ?? null],
633
- [t('payment_gateways.transactions.log.entityType', 'Entity Type'), log.scopeEntityType ?? null],
634
- [t('payment_gateways.transactions.log.entityId', 'Entity ID'), log.scopeEntityId ?? null],
635
- ].filter((entry): entry is [string, string] => typeof entry[1] === 'string' && entry[1].trim().length > 0)
636
- const { inlineEntries, nestedEntries } = splitLogPayload(log.payload)
637
-
638
- return (
639
- <React.Fragment key={log.id}>
640
- <tr className="border-b last:border-0">
641
- <td className="whitespace-nowrap px-4 py-2 text-muted-foreground">{formatDateTime(log.createdAt)}</td>
642
- <td className="px-4 py-2">
643
- <Badge variant="secondary" className={LOG_LEVEL_STYLES[log.level] ?? ''}>
644
- {t(`payment_gateways.transactions.level.${log.level}`, log.level)}
645
- </Badge>
646
- </td>
647
- <td className="px-4 py-2">
648
- <Button
649
- type="button"
650
- variant="ghost"
651
- size="sm"
652
- className="h-auto w-full justify-start gap-2 px-0 py-0 text-left hover:bg-transparent"
653
- onClick={() => setExpandedLogId((current) => current === log.id ? null : log.id)}
654
- >
655
- {isExpanded ? <ChevronDown className="h-4 w-4 shrink-0" /> : <ChevronRight className="h-4 w-4 shrink-0" />}
656
- <span className="truncate">{log.message}</span>
657
- </Button>
658
- </td>
659
- </tr>
660
- {isExpanded ? (
661
- <tr className="border-b bg-muted/30 last:border-0">
662
- <td colSpan={3} className="px-4 py-4">
663
- <div className="space-y-4 rounded-lg border bg-card p-4">
664
- {metadataEntries.length > 0 ? (
665
- <dl className="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
666
- {metadataEntries.map(([label, value]) => (
667
- <div key={label} className="rounded-md border bg-muted/30 px-3 py-2">
668
- <dt className="text-overline font-medium uppercase tracking-wide text-muted-foreground">{label}</dt>
669
- <dd className="mt-1 break-all text-sm">{value}</dd>
670
- </div>
671
- ))}
672
- </dl>
673
- ) : null}
674
- {inlineEntries.length > 0 ? (
675
- <dl className="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
676
- {inlineEntries.map(([key, value]) => (
677
- <div key={key} className="rounded-md border bg-muted/30 px-3 py-2">
678
- <dt className="text-overline font-medium uppercase tracking-wide text-muted-foreground">{formatLogDetailLabel(key)}</dt>
679
- <dd className="mt-1 break-words text-sm">{formatLogPrimitiveValue(value)}</dd>
680
- </div>
681
- ))}
682
- </dl>
683
- ) : null}
684
- {nestedEntries.map(([key, value]) => (
685
- <JsonDisplay
686
- key={key}
687
- data={value}
688
- title={formatLogDetailLabel(key)}
689
- defaultExpanded
690
- maxInitialDepth={1}
691
- theme="dark"
692
- maxHeight="16rem"
693
- className="p-4"
694
- />
695
- ))}
696
- </div>
697
- </td>
698
- </tr>
699
- ) : null}
700
- </React.Fragment>
701
- )
702
- })}
703
- </tbody>
704
- </table>
705
- </div>
706
- )}
606
+ <LogList
607
+ entries={detail.logs.map<LogListEntry>((log) => {
608
+ const metadataEntries = [
609
+ [t('payment_gateways.transactions.log.time', 'Time'), formatDateTime(log.createdAt)],
610
+ [t('payment_gateways.transactions.log.level', 'Level'), t(`payment_gateways.transactions.level.${log.level}`, log.level)],
611
+ [t('payment_gateways.transactions.log.code', 'Code'), log.code ?? null],
612
+ [t('payment_gateways.transactions.log.runId', 'Run ID'), log.runId ?? null],
613
+ [t('payment_gateways.transactions.log.entityType', 'Entity Type'), log.scopeEntityType ?? null],
614
+ [t('payment_gateways.transactions.log.entityId', 'Entity ID'), log.scopeEntityId ?? null],
615
+ ].filter((entry): entry is [string, string] => typeof entry[1] === 'string' && entry[1].trim().length > 0)
616
+ const { inlineEntries, nestedEntries } = splitLogPayload(log.payload)
617
+
618
+ return {
619
+ id: log.id,
620
+ time: formatDateTime(log.createdAt),
621
+ level: log.level,
622
+ levelLabel: t(`payment_gateways.transactions.level.${log.level}`, log.level),
623
+ message: log.message,
624
+ body: (
625
+ <div className="space-y-4">
626
+ {metadataEntries.length > 0 ? (
627
+ <dl className="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
628
+ {metadataEntries.map(([label, value]) => (
629
+ <div key={label} className="rounded-md border bg-muted/30 px-3 py-2">
630
+ <dt className="text-overline font-medium uppercase tracking-wide text-muted-foreground">{label}</dt>
631
+ <dd className="mt-1 break-all text-sm">{value}</dd>
632
+ </div>
633
+ ))}
634
+ </dl>
635
+ ) : null}
636
+ {inlineEntries.length > 0 ? (
637
+ <dl className="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
638
+ {inlineEntries.map(([key, value]) => (
639
+ <div key={key} className="rounded-md border bg-muted/30 px-3 py-2">
640
+ <dt className="text-overline font-medium uppercase tracking-wide text-muted-foreground">{formatLogDetailLabel(key)}</dt>
641
+ <dd className="mt-1 break-words text-sm">{formatLogPrimitiveValue(value)}</dd>
642
+ </div>
643
+ ))}
644
+ </dl>
645
+ ) : null}
646
+ {nestedEntries.map(([key, value]) => (
647
+ <JsonDisplay
648
+ key={key}
649
+ data={value}
650
+ title={formatLogDetailLabel(key)}
651
+ defaultExpanded
652
+ maxInitialDepth={1}
653
+ theme="dark"
654
+ maxHeight="16rem"
655
+ className="p-4"
656
+ />
657
+ ))}
658
+ </div>
659
+ ),
660
+ }
661
+ })}
662
+ emptyMessage={t('payment_gateways.transactions.detail.logsEmpty', 'No transaction-scoped logs are available yet.')}
663
+ />
707
664
  </section>
708
665
  </div>
709
666
 
@@ -73,7 +73,7 @@ export function AvailabilityRuleSetForm(props: AvailabilityRuleSetFormProps) {
73
73
  id: 'description',
74
74
  label: translate('planner.availabilityRuleSets.form.fields.description', 'Description'),
75
75
  type: 'richtext',
76
- editor: 'uiw',
76
+ editor: 'html',
77
77
  },
78
78
  ], [translate])
79
79
 
@@ -18,7 +18,7 @@ import { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
18
18
  import { createCrud, deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'
19
19
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
20
20
  import { Spinner } from '@open-mercato/ui/primitives/spinner'
21
- import { ComboboxInput } from '@open-mercato/ui/backend/inputs'
21
+ import { ComboboxInput, TimePicker } from '@open-mercato/ui/backend/inputs'
22
22
  import { DictionaryEntrySelect, type DictionarySelectLabels } from '@open-mercato/core/modules/dictionaries/components/DictionaryEntrySelect'
23
23
  import {
24
24
  createUnavailabilityReasonEntry,
@@ -1425,20 +1425,20 @@ export function AvailabilityRulesEditor({
1425
1425
  return (
1426
1426
  <div key={`${day.code}-${windowIndex}`} className="space-y-1">
1427
1427
  <div className="flex flex-wrap items-center gap-2">
1428
- <Input
1429
- type="time"
1428
+ <TimePicker
1430
1429
  value={window.start}
1431
- onChange={(event) => handleWeeklyWindowChange(index, windowIndex, { ...window, start: event.target.value })}
1432
- className={`h-9 w-[120px] ${errorClass}`}
1430
+ onChange={(value) => handleWeeklyWindowChange(index, windowIndex, { ...window, start: value ?? '' })}
1431
+ className={`w-[120px] ${errorClass}`}
1433
1432
  disabled={usingRuleSet || isReadOnly}
1433
+ showClearButton={false}
1434
1434
  />
1435
1435
  <span className="text-sm text-muted-foreground">-</span>
1436
- <Input
1437
- type="time"
1436
+ <TimePicker
1438
1437
  value={window.end}
1439
- onChange={(event) => handleWeeklyWindowChange(index, windowIndex, { ...window, end: event.target.value })}
1440
- className={`h-9 w-[120px] ${errorClass}`}
1438
+ onChange={(value) => handleWeeklyWindowChange(index, windowIndex, { ...window, end: value ?? '' })}
1439
+ className={`w-[120px] ${errorClass}`}
1441
1440
  disabled={usingRuleSet || isReadOnly}
1441
+ showClearButton={false}
1442
1442
  />
1443
1443
  <Button
1444
1444
  type="button"
@@ -1753,18 +1753,18 @@ export function AvailabilityRulesEditor({
1753
1753
  return (
1754
1754
  <div key={`${index}-${window.start}`} className="space-y-1">
1755
1755
  <div className="flex flex-wrap items-center gap-2">
1756
- <Input
1757
- type="time"
1756
+ <TimePicker
1758
1757
  value={window.start}
1759
- onChange={(event) => handleEditorWindowChange(index, { ...window, start: event.target.value })}
1760
- className={`h-9 w-[120px] ${errorClass}`}
1758
+ onChange={(value) => handleEditorWindowChange(index, { ...window, start: value ?? '' })}
1759
+ className={`w-[120px] ${errorClass}`}
1760
+ showClearButton={false}
1761
1761
  />
1762
1762
  <span className="text-sm text-muted-foreground">-</span>
1763
- <Input
1764
- type="time"
1763
+ <TimePicker
1765
1764
  value={window.end}
1766
- onChange={(event) => handleEditorWindowChange(index, { ...window, end: event.target.value })}
1767
- className={`h-9 w-[120px] ${errorClass}`}
1765
+ onChange={(value) => handleEditorWindowChange(index, { ...window, end: value ?? '' })}
1766
+ className={`w-[120px] ${errorClass}`}
1767
+ showClearButton={false}
1768
1768
  />
1769
1769
  <Button
1770
1770
  type="button"
@@ -131,7 +131,7 @@ export function useResourcesResourceFormConfig(options: {
131
131
  id: 'description',
132
132
  label: t('resources.resources.form.fields.description', 'Description'),
133
133
  type: 'richtext',
134
- editor: 'uiw',
134
+ editor: 'html',
135
135
  },
136
136
  {
137
137
  id: 'resourceTypeId',
@@ -78,7 +78,7 @@ export function ResourceTypeCrudForm({
78
78
 
79
79
  const fields = React.useMemo<CrudField[]>(() => [
80
80
  { id: 'name', label: t('resources.resourceTypes.form.name', 'Name'), type: 'text', required: true },
81
- { id: 'description', label: t('resources.resourceTypes.form.description', 'Description'), type: 'richtext' },
81
+ { id: 'description', label: t('resources.resourceTypes.form.description', 'Description'), type: 'richtext', editor: 'html' },
82
82
  {
83
83
  id: 'appearance',
84
84
  label: t('resources.resourceTypes.form.appearance.label', 'Appearance'),