All files / layers / L2R / surfaces DetailSurface.tsx

89.85% Statements 62/69
72.58% Branches 45/62
90.9% Functions 10/11
93.54% Lines 58/62

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        73x   73x   174x       174x 174x 274x 1264x 1264x 1264x 1416x   270x   1264x     174x     448x 448x 448x 448x           448x 448x 448x 448x 288x 288x 2x 2x   288x 3x               448x 204x 98x 76x   448x 278x 174x   448x 448x 448x 360x   448x 360x         448x 124x   448x                                 448x 448x                         73x 35x   73x   73x   73x   73x 73x           73x 73x 73x 73x             73x 73x                
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)
    if (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 }