Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | 67x 67x 170x 170x 170x 274x 1244x 1244x 1244x 1404x 258x 1244x 170x 41x 442x 442x 442x 442x 102x 102x 102x 442x 442x 442x 442x 284x 284x 2x 2x 41x 284x 3x 442x 198x 98x 76x 442x 274x 170x 53x 442x 442x 442x 350x 53x 442x 350x 4x 4x 21x 442x 126x 442x 53x 47x 6x 6x 53x 47x 41x 53x 442x 442x 53x 53x 44x 53x 17x 67x 34x 67x 67x 3x 67x 67x 67x 67x 67x 67x 67x 53x 67x 67x | import { useEffect, useRef, useMemo, type ReactNode } from 'react'
import { useLocation } from 'react-router-dom'
import type {
DetailSurfaceProps,
DetailConfig,
CoordinatorSnapshot,
DetailVisualOverrides,
} from '../../interfaces'
import {
createDefaultEditNavHandler,
type NavigatorFn,
} from '../wiring/defaultEditNav'
import { CrudReadCard } from '@/shared/components/CrudReadCard'
import type { MetadataField } from '@/shared/components/MetadataCard'
export interface DetailSurfaceExtendedProps<TForm> extends DetailSurfaceProps<TForm> {
entity: { id: string; [key: string]: unknown } | null
isLoading: boolean
error: unknown
navigate: NavigatorFn
extra?: ReactNode
}
function buildFields(
config: DetailConfig,
entity: Record<string, unknown>,
): MetadataField[] {
const groups = config.readGrouping ?? [{ label: 'Details', fields: Object.keys(entity) }]
const fields: MetadataField[] = []
for (const group of groups) {
for (const fieldKey of group.fields) {
const formatter = config.fieldFormatters?.[fieldKey]
const field: MetadataField = {
label: fieldKey,
}
if (formatter) {
field.render = () => formatter(entity[fieldKey], entity)
} else {
field.value = entity[fieldKey] != null ? String(entity[fieldKey]) : ''
}
I fields.push(field)
}
}
return fields
}
export function DetailSurface<TForm>({
config,
routeContext,
overrides,
entity,
isLoading,
error,
navigate,
onDeleteSuccess,
coordinatorSnapshot,
extra,
}: DetailSurfaceExtendedProps<TForm>) {
const id = routeContext.page === 'detail' ? routeContext.id : entity?.id ?? ''
const location = useLocation()
const editNavHandler = createDefaultEditNavHandler({ config, id, navigate, pathname: location.pathname })
const deleteAction = config.actionTargets?.delete
const deleteState = coordinatorSnapshot.deleteState
const wasDeletingRef = useRef(false)
useEffect(() => {
if (!deleteState) return
if (wasDeletingRef.current && !deleteState.isPending && !deleteState.error) {
wasDeletingRef.current = false
onDeleteSuccess?.()
}
if (deleteState.isPending) {
wasDeletingRef.current = true
}
}, [deleteState, deleteState?.isPending, deleteState?.error, onDeleteSuccess])
const title = useMemo(() => {
if (entity && 'name' in entity) return String(entity.name)
Iif (entity) return entity.id
return config.title ?? ''
}, [entity, config.title])
const fields = useMemo(() => {
if (!entity) return []
return buildFields(config, entity)
}, [config, entity])
const is404 = !isLoading && !error && !entity
const actions: {
edit?: { onClick: () => void }
delete?: {
onClick: () => void
entityName: string
disabled?: boolean
disabledReason?: string
}
extra?: ReactNode
} = {}
if (config.editTargetOverride || config.actionTargets?.edit) {
actions.edit = { onClick: editNavHandler }
}
if (deleteAction && deleteState) {
actions.delete = {
onClick: deleteState.requestDelete,
entityName: (entity as Record<string, unknown>)?.name as string ?? 'this entity',
}
}
if (extra) {
actions.extra = extra
}
E const content = (
I <CrudReadCard
title={title}
fields={fields}
actions={actions}
isLoading={isLoading}
error={error instanceof Error ? error : error ? new Error(String(error)) : null}
is404={is404}
onRetry={undefined}
backPath={`/${config.entityPath}`}
deleteState={deleteState}
deleteError={deleteState?.error}
overrides={overrides}
/>
)
const Wrapper = overrides?.layoutWrapper
return (
<div data-testid="detail-surface">
{Wrapper ? <Wrapper>{content}</Wrapper> : content}
</div>
)
}
export { type DetailSurfaceProps, type CoordinatorSnapshot, type DetailVisualOverrides, type DetailConfig }
|