@asteby/metacore-runtime-react 20.1.6 → 20.1.7
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/CHANGELOG.md +18 -0
- package/dist/dynamic-columns.d.ts +12 -0
- package/dist/dynamic-columns.d.ts.map +1 -1
- package/dist/dynamic-columns.js +31 -20
- package/dist/dynamic-kanban.d.ts +8 -1
- package/dist/dynamic-kanban.d.ts.map +1 -1
- package/dist/dynamic-kanban.js +27 -17
- package/dist/dynamic-row-actions.d.ts +36 -0
- package/dist/dynamic-row-actions.d.ts.map +1 -0
- package/dist/dynamic-row-actions.js +110 -0
- package/dist/dynamic-table.d.ts.map +1 -1
- package/dist/dynamic-table.js +11 -76
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/permissions-context.d.ts +15 -1
- package/dist/permissions-context.d.ts.map +1 -1
- package/dist/permissions-context.js +29 -0
- package/package.json +1 -1
- package/src/__tests__/dynamic-kanban.test.tsx +15 -5
- package/src/__tests__/resolve-row-actions.test.ts +80 -0
- package/src/dynamic-columns.tsx +33 -19
- package/src/dynamic-kanban.tsx +48 -38
- package/src/dynamic-row-actions.tsx +195 -0
- package/src/dynamic-table.tsx +12 -107
- package/src/index.ts +6 -0
- package/src/permissions-context.tsx +39 -0
package/src/dynamic-table.tsx
CHANGED
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
// `getDynamicColumns` prop (hosts retain ownership because the rendered
|
|
12
12
|
// column cells are tightly coupled to their design system).
|
|
13
13
|
import { useEffect, useState, useMemo, useCallback, useRef } from 'react'
|
|
14
|
-
import { useNavigate } from '@tanstack/react-router'
|
|
15
14
|
import { useTranslation } from 'react-i18next'
|
|
16
15
|
import { format } from 'date-fns'
|
|
17
16
|
import type { DateRange } from 'react-day-picker'
|
|
@@ -68,11 +67,10 @@ import { useApi, useCurrentBranch } from './api-context'
|
|
|
68
67
|
import type { ColumnFilterConfig, GetDynamicColumns } from './dynamic-columns-shim'
|
|
69
68
|
import { defaultGetDynamicColumns, DATE_CELL_TYPES, aggregateOf, formatAggregateTotal } from './dynamic-columns'
|
|
70
69
|
import { OptionsContext } from './options-context'
|
|
71
|
-
import {
|
|
72
|
-
import type { TableMetadata, ApiResponse, ActionMetadata } from './types'
|
|
70
|
+
import type { TableMetadata, ApiResponse } from './types'
|
|
73
71
|
import { getSearchableColumnKeys } from './column-visibility'
|
|
74
72
|
import { useCan, usePermissionsActive, gateTableMetadata } from './permissions-context'
|
|
75
|
-
import {
|
|
73
|
+
import { useDynamicRowActions } from './dynamic-row-actions'
|
|
76
74
|
import { ExportDialog } from './dialogs/export'
|
|
77
75
|
import { ImportDialog } from './dialogs/import'
|
|
78
76
|
|
|
@@ -131,7 +129,6 @@ export function DynamicTable({
|
|
|
131
129
|
const { t, i18n } = useTranslation()
|
|
132
130
|
const api = useApi()
|
|
133
131
|
const currentBranch = useCurrentBranch()
|
|
134
|
-
const navigate = useNavigate()
|
|
135
132
|
|
|
136
133
|
const prevBranchId = useRef(currentBranch?.id)
|
|
137
134
|
|
|
@@ -147,24 +144,9 @@ export function DynamicTable({
|
|
|
147
144
|
const [loadingData, setLoadingData] = useState(true)
|
|
148
145
|
const [optionsMap, setOptionsMap] = useState<Map<string, any[]>>(new Map())
|
|
149
146
|
|
|
150
|
-
const [recordDialog, setRecordDialog] = useState<{
|
|
151
|
-
open: boolean
|
|
152
|
-
mode: 'view' | 'edit' | 'create'
|
|
153
|
-
recordId: string | null
|
|
154
|
-
}>({ open: false, mode: 'view', recordId: null })
|
|
155
|
-
|
|
156
|
-
const [rowToDelete, setRowToDelete] = useState<any | null>(null)
|
|
157
|
-
const [isDeleting, setIsDeleting] = useState(false)
|
|
158
|
-
|
|
159
147
|
const [exportOpen, setExportOpen] = useState(false)
|
|
160
148
|
const [importOpen, setImportOpen] = useState(false)
|
|
161
149
|
|
|
162
|
-
const [actionModal, setActionModal] = useState<{
|
|
163
|
-
open: boolean
|
|
164
|
-
action: ActionMetadata | null
|
|
165
|
-
record: any | null
|
|
166
|
-
}>({ open: false, action: null, record: null })
|
|
167
|
-
|
|
168
150
|
const [showBulkDeleteConfirm, setShowBulkDeleteConfirm] = useState(false)
|
|
169
151
|
const [isBulkDeleting, setIsBulkDeleting] = useState(false)
|
|
170
152
|
const [bulkDeleteProgress, setBulkDeleteProgress] = useState(0)
|
|
@@ -506,58 +488,15 @@ export function DynamicTable({
|
|
|
506
488
|
|
|
507
489
|
const handleRefresh = useCallback(() => { fetchData(); fetchAggregates() }, [fetchData, fetchAggregates])
|
|
508
490
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
const url = linkDef.linkUrl.replace(/\{(\w+)\}/g, (_: string, field: string) => String(row[field] ?? ''))
|
|
519
|
-
navigate({ to: url })
|
|
520
|
-
return
|
|
521
|
-
}
|
|
522
|
-
const actionDef = metadata?.actions?.find((a) => a.key === action)
|
|
523
|
-
if (actionDef && (actionDef.fields?.length || actionDef.confirm || actionDef.executable)) {
|
|
524
|
-
setActionModal({
|
|
525
|
-
open: true,
|
|
526
|
-
action: {
|
|
527
|
-
key: actionDef.key,
|
|
528
|
-
label: actionDef.label,
|
|
529
|
-
icon: actionDef.icon || 'Zap',
|
|
530
|
-
color: actionDef.color,
|
|
531
|
-
confirm: actionDef.confirm,
|
|
532
|
-
confirmMessage: actionDef.confirmMessage,
|
|
533
|
-
fields: actionDef.fields,
|
|
534
|
-
requiresState: actionDef.requiresState,
|
|
535
|
-
executable: actionDef.executable,
|
|
536
|
-
},
|
|
537
|
-
record: row,
|
|
538
|
-
})
|
|
539
|
-
return
|
|
540
|
-
}
|
|
541
|
-
if (onAction) { await Promise.resolve(onAction(action, row)); handleRefresh() }
|
|
542
|
-
else handleRefresh()
|
|
543
|
-
}, [onAction, handleRefresh, metadata, navigate])
|
|
544
|
-
|
|
545
|
-
const confirmDelete = async () => {
|
|
546
|
-
if (!rowToDelete) return
|
|
547
|
-
setIsDeleting(true)
|
|
548
|
-
try {
|
|
549
|
-
const deleteEndpoint = endpoint ? `${endpoint}/${rowToDelete.id}` : `/data/${model}/${rowToDelete.id}`
|
|
550
|
-
const res = await api.delete(deleteEndpoint)
|
|
551
|
-
if (res.data.success) { toast.success(res.data.message || 'Eliminado correctamente'); handleRefresh() }
|
|
552
|
-
else toast.error(res.data.message || 'Error al eliminar')
|
|
553
|
-
} catch (error) {
|
|
554
|
-
console.error('Error al eliminar', error)
|
|
555
|
-
toast.error('Error al eliminar el registro')
|
|
556
|
-
} finally {
|
|
557
|
-
setIsDeleting(false)
|
|
558
|
-
setRowToDelete(null)
|
|
559
|
-
}
|
|
560
|
-
}
|
|
491
|
+
// Per-row action dispatch (view/edit/delete/link/custom) + its dialogs live
|
|
492
|
+
// in the shared hook so DynamicKanban's card menu behaves identically.
|
|
493
|
+
const { handleInternalAction, dialogs: rowActionDialogs } = useDynamicRowActions({
|
|
494
|
+
model,
|
|
495
|
+
endpoint,
|
|
496
|
+
metadata,
|
|
497
|
+
onAction,
|
|
498
|
+
onRefresh: handleRefresh,
|
|
499
|
+
})
|
|
561
500
|
|
|
562
501
|
const confirmBulkDelete = async () => {
|
|
563
502
|
const selectedRows = table.getFilteredSelectedRowModel().rows
|
|
@@ -974,20 +913,7 @@ export function DynamicTable({
|
|
|
974
913
|
</div>
|
|
975
914
|
</div>
|
|
976
915
|
|
|
977
|
-
|
|
978
|
-
<AlertDialogContent>
|
|
979
|
-
<AlertDialogHeader>
|
|
980
|
-
<AlertDialogTitle>¿Está absolutamente seguro?</AlertDialogTitle>
|
|
981
|
-
<AlertDialogDescription>Esta acción no se puede deshacer. Esto eliminará permanentemente el registro seleccionado de nuestros servidores.</AlertDialogDescription>
|
|
982
|
-
</AlertDialogHeader>
|
|
983
|
-
<AlertDialogFooter>
|
|
984
|
-
<AlertDialogCancel disabled={isDeleting}>{t('common.cancel')}</AlertDialogCancel>
|
|
985
|
-
<AlertDialogAction onClick={(e: React.MouseEvent) => { e.preventDefault(); confirmDelete() }} className="bg-red-600 hover:bg-red-700" disabled={isDeleting}>
|
|
986
|
-
{isDeleting ? 'Eliminando...' : 'Eliminar'}
|
|
987
|
-
</AlertDialogAction>
|
|
988
|
-
</AlertDialogFooter>
|
|
989
|
-
</AlertDialogContent>
|
|
990
|
-
</AlertDialog>
|
|
916
|
+
{rowActionDialogs}
|
|
991
917
|
|
|
992
918
|
<AlertDialog open={showBulkDeleteConfirm} onOpenChange={(open: boolean) => !open && !isBulkDeleting && setShowBulkDeleteConfirm(false)}>
|
|
993
919
|
<AlertDialogContent>
|
|
@@ -1013,33 +939,12 @@ export function DynamicTable({
|
|
|
1013
939
|
</AlertDialogContent>
|
|
1014
940
|
</AlertDialog>
|
|
1015
941
|
|
|
1016
|
-
<DynamicRecordDialog
|
|
1017
|
-
open={recordDialog.open}
|
|
1018
|
-
onOpenChange={(open: boolean) => setRecordDialog((prev) => ({ ...prev, open }))}
|
|
1019
|
-
mode={recordDialog.mode}
|
|
1020
|
-
model={model}
|
|
1021
|
-
recordId={recordDialog.recordId}
|
|
1022
|
-
endpoint={endpoint}
|
|
1023
|
-
onSaved={handleRefresh}
|
|
1024
|
-
/>
|
|
1025
|
-
|
|
1026
942
|
{viewMetadata?.canExport && (
|
|
1027
943
|
<ExportDialog open={exportOpen} onOpenChange={setExportOpen} model={model} metadata={metadata} currentFilters={buildFilterParams()} hasActiveFilters={hasActiveFilters} />
|
|
1028
944
|
)}
|
|
1029
945
|
{viewMetadata?.canImport && (
|
|
1030
946
|
<ImportDialog open={importOpen} onOpenChange={setImportOpen} model={model} metadata={metadata} onImported={handleRefresh} />
|
|
1031
947
|
)}
|
|
1032
|
-
{actionModal.action && (
|
|
1033
|
-
<ActionModalDispatcher
|
|
1034
|
-
open={actionModal.open}
|
|
1035
|
-
onOpenChange={(open: boolean) => setActionModal((prev) => ({ ...prev, open }))}
|
|
1036
|
-
action={actionModal.action}
|
|
1037
|
-
model={model}
|
|
1038
|
-
record={actionModal.record}
|
|
1039
|
-
endpoint={endpoint}
|
|
1040
|
-
onSuccess={handleRefresh}
|
|
1041
|
-
/>
|
|
1042
|
-
)}
|
|
1043
948
|
<DataTableBulkActions table={table} entityName="registro">
|
|
1044
949
|
<Button variant="destructive" size="sm" className="h-8" onClick={() => setShowBulkDeleteConfirm(true)}>
|
|
1045
950
|
<Trash2 className="h-4 w-4 mr-1.5" /> Eliminar
|
package/src/index.ts
CHANGED
|
@@ -53,9 +53,15 @@ export {
|
|
|
53
53
|
capabilityForActionKey,
|
|
54
54
|
modelCapability,
|
|
55
55
|
gateTableMetadata,
|
|
56
|
+
resolveRowActions,
|
|
56
57
|
type CanFn,
|
|
57
58
|
type PermissionsProviderProps,
|
|
58
59
|
} from './permissions-context'
|
|
60
|
+
export {
|
|
61
|
+
useDynamicRowActions,
|
|
62
|
+
type UseDynamicRowActionsParams,
|
|
63
|
+
type DynamicRowActions,
|
|
64
|
+
} from './dynamic-row-actions'
|
|
59
65
|
export {
|
|
60
66
|
PermissionsManager,
|
|
61
67
|
moduleActionCapability,
|
|
@@ -156,3 +156,42 @@ export function gateTableMetadata(
|
|
|
156
156
|
metadata.canCreate === undefined ? undefined : metadata.canCreate && allowed('create'),
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Resolves the per-row (`placement: 'row'`) action list for a model exactly the
|
|
162
|
+
* way DynamicTable's action column does, so the kanban card menu shows the SAME
|
|
163
|
+
* actions:
|
|
164
|
+
* - when a <PermissionsProvider> is active, the metadata is run through
|
|
165
|
+
* `gateTableMetadata` first (filters by capability + materializes the CRUD
|
|
166
|
+
* trio), otherwise the raw metadata is used.
|
|
167
|
+
* - explicit actions win; absent them, the implicit View/Edit/Delete trio is
|
|
168
|
+
* materialized when `enableCRUDActions` is on.
|
|
169
|
+
* - table/create-placement actions are stripped (they belong to the toolbar).
|
|
170
|
+
*
|
|
171
|
+
* Pure. `tx` resolves the trio's i18n labels (defaults to the Spanish fallbacks).
|
|
172
|
+
*/
|
|
173
|
+
export function resolveRowActions(
|
|
174
|
+
metadata: TableMetadata,
|
|
175
|
+
model: string,
|
|
176
|
+
can: CanFn,
|
|
177
|
+
permissionsActive: boolean,
|
|
178
|
+
tx: (i18nKey: string, fallback: string) => string = (_k, fallback) => fallback,
|
|
179
|
+
): ActionDefinition[] {
|
|
180
|
+
const gated = permissionsActive ? gateTableMetadata(metadata, model, can, tx) : metadata
|
|
181
|
+
const explicit = gated.actions ?? []
|
|
182
|
+
const hasExplicit = (gated.hasActions ?? explicit.length > 0) && explicit.length > 0
|
|
183
|
+
const base: ActionDefinition[] = hasExplicit
|
|
184
|
+
? explicit
|
|
185
|
+
: gated.enableCRUDActions
|
|
186
|
+
? DEFAULT_TRIO.map(
|
|
187
|
+
(a) =>
|
|
188
|
+
({
|
|
189
|
+
key: a.key,
|
|
190
|
+
name: a.key,
|
|
191
|
+
label: tx(a.i18nKey, a.fallback),
|
|
192
|
+
icon: a.icon,
|
|
193
|
+
}) as ActionDefinition,
|
|
194
|
+
)
|
|
195
|
+
: []
|
|
196
|
+
return base.filter((a) => ((a.placement ?? 'row') as string) === 'row')
|
|
197
|
+
}
|