Files
payloadcms/packages/ui/src/elements/Table/DefaultCell/fields/Relationship/index.tsx
Alessio Gravili 03291472d6 chore: bump all eslint dependencies, run lint and prettier (#9128)
This fixes a peer dependency error in our monorepo, as
eslint-plugin-jsx-a11y finally supports eslint v9.

Additionally, this officially adds TypeScript 5.6 support for
typescript-eslint.
2024-11-12 10:18:22 -05:00

148 lines
4.9 KiB
TypeScript

'use client'
import type {
ClientCollectionConfig,
DefaultCellComponentProps,
JoinFieldClient,
RelationshipFieldClient,
UploadFieldClient,
} from 'payload'
import { getTranslation } from '@payloadcms/translations'
import React, { useEffect, useMemo, useState } from 'react'
import { useIntersect } from '../../../../../hooks/useIntersect.js'
import { useConfig } from '../../../../../providers/Config/index.js'
import { useTranslation } from '../../../../../providers/Translation/index.js'
import { canUseDOM } from '../../../../../utilities/canUseDOM.js'
import { formatDocTitle } from '../../../../../utilities/formatDocTitle.js'
import { useListRelationships } from '../../../RelationshipProvider/index.js'
import { FileCell } from '../File/index.js'
import './index.scss'
type Value = { relationTo: string; value: number | string }
const baseClass = 'relationship-cell'
const totalToShow = 3
export type RelationshipCellProps = DefaultCellComponentProps<
any,
JoinFieldClient | RelationshipFieldClient | UploadFieldClient
>
export const RelationshipCell: React.FC<RelationshipCellProps> = ({
cellData: cellDataFromProps,
customCellProps: customCellContext,
field,
field: { label },
}) => {
// conditionally extract relationTo both both relationship and join fields
const relationTo =
('relationTo' in field && field.relationTo) || ('collection' in field && field.collection)
// conditionally extract docs from join fields
const cellData = useMemo(() => {
return 'collection' in field ? cellDataFromProps?.docs : cellDataFromProps
}, [cellDataFromProps, field])
const { config, getEntityConfig } = useConfig()
const { collections, routes } = config
const [intersectionRef, entry] = useIntersect()
const [values, setValues] = useState<Value[]>([])
const { documents, getRelationships } = useListRelationships()
const [hasRequested, setHasRequested] = useState(false)
const { i18n, t } = useTranslation()
const isAboveViewport = canUseDOM ? entry?.boundingClientRect?.top < window.innerHeight : false
useEffect(() => {
if ((cellData || typeof cellData === 'number') && isAboveViewport && !hasRequested) {
const formattedValues: Value[] = []
const arrayCellData = Array.isArray(cellData) ? cellData : [cellData]
arrayCellData
.slice(0, arrayCellData.length < totalToShow ? arrayCellData.length : totalToShow)
.forEach((cell) => {
if (typeof cell === 'object' && 'relationTo' in cell && 'value' in cell) {
formattedValues.push(cell)
}
if (
(typeof cell === 'number' || typeof cell === 'string') &&
typeof relationTo === 'string'
) {
formattedValues.push({
relationTo,
value: cell,
})
}
})
getRelationships(formattedValues)
setHasRequested(true)
setValues(formattedValues)
}
}, [
cellData,
relationTo,
collections,
isAboveViewport,
routes.api,
hasRequested,
getRelationships,
])
useEffect(() => {
if (hasRequested) {
setHasRequested(false)
}
}, [cellData])
return (
<div className={baseClass} ref={intersectionRef}>
{values.map(({ relationTo, value }, i) => {
const document = documents[relationTo][value]
const relatedCollection = getEntityConfig({
collectionSlug: relationTo,
}) as ClientCollectionConfig
const label = formatDocTitle({
collectionConfig: relatedCollection,
data: document || null,
dateFormat: config.admin.dateFormat,
fallback: `${t('general:untitled')} - ID: ${value}`,
i18n,
})
let fileField = null
if (field.type === 'upload') {
const relatedCollectionPreview = !!relatedCollection.upload.displayPreview
const previewAllowed =
field.displayPreview || (relatedCollectionPreview && field.displayPreview !== false)
if (previewAllowed && document) {
fileField = (
<FileCell
cellData={label}
collectionConfig={relatedCollection}
customCellProps={customCellContext}
field={field}
rowData={document}
/>
)
}
}
return (
<React.Fragment key={i}>
{document === false && `${t('general:untitled')} - ID: ${value}`}
{document === null && `${t('general:loading')}...`}
{document ? fileField || label : null}
{values.length > i + 1 && ', '}
</React.Fragment>
)
})}
{Array.isArray(cellData) &&
cellData.length > totalToShow &&
t('fields:itemsAndMore', { count: cellData.length - totalToShow, items: '' })}
{values.length === 0 && t('general:noLabel', { label: getTranslation(label || '', i18n) })}
</div>
)
}