All files / layers / L2R / surfaces DetailSurface.tsx

92.24% Statements 107/116
75.89% Branches 85/112
94.11% Functions 16/17
93.1% Lines 81/87

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        59x   59x   122x       122x 122x 170x 888x 888x 888x 1012x   186x   888x     122x   37x 326x 326x 326x 326x 82x 82x     82x 326x 326x 326x 326x 210x 210x     37x 210x 2x               326x 152x 80x 58x   326x 194x 122x 49x 326x 326x 326x 238x 49x 326x 238x 4x 4x   21x 326x 56x   326x 49x 43x 6x 6x     49x 43x 37x     49x         326x 326x         49x   49x 40x     49x 17x 59x 27x   59x   59x 3x 59x   59x 59x           59x 59x 59x 59x           49x 59x 59x                
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)
  I  }
  }

  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 }