feat(ui): adds edit many option for bulk uploads (#10646)
### What? This PR introduces the ability to bulk edit multiple uploads simultaneously within the `Edit all` option for bulk uploads. Users can now select fields to update across all selected uploads in a single operation. ### Why? Managing multiple uploads individually can be time-consuming and inefficient, especially when updating common fields. This feature streamlines the process, improving user experience and productivity when handling bulk uploads. ### How? * Added an `Edit Many` drawer component specific to bulk uploads that allows users to select fields for bulk editing. * Enhanced the FormsManager and related logic to ensure updates are applied consistently across all selected uploads. 
This commit is contained in:
@@ -1,17 +1,24 @@
|
||||
'use client'
|
||||
|
||||
import type { ClientCollectionConfig } from 'payload'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import { ChevronIcon } from '../../../icons/Chevron/index.js'
|
||||
import { useConfig } from '../../../providers/Config/index.js'
|
||||
import { useTranslation } from '../../../providers/Translation/index.js'
|
||||
import { Button } from '../../Button/index.js'
|
||||
import { EditManyBulkUploads } from '../EditMany/index.js'
|
||||
import { useFormsManager } from '../FormsManager/index.js'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'bulk-upload--actions-bar'
|
||||
|
||||
export function ActionsBar() {
|
||||
type Props = {
|
||||
readonly collectionConfig: ClientCollectionConfig
|
||||
}
|
||||
|
||||
export function ActionsBar({ collectionConfig }: Props) {
|
||||
const { activeIndex, forms, setActiveIndex } = useFormsManager()
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -56,6 +63,7 @@ export function ActionsBar() {
|
||||
<ChevronIcon direction="right" />
|
||||
</Button>
|
||||
</div>
|
||||
<EditManyBulkUploads collection={collectionConfig} />
|
||||
</div>
|
||||
|
||||
<Actions className={`${baseClass}__saveButtons`} />
|
||||
|
||||
@@ -36,7 +36,7 @@ export function AddingFilesView() {
|
||||
const { user } = useAuth()
|
||||
const { openModal } = useModal()
|
||||
|
||||
const collection = getEntityConfig({ collectionSlug })
|
||||
const collectionConfig = getEntityConfig({ collectionSlug })
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
@@ -45,7 +45,7 @@ export function AddingFilesView() {
|
||||
<div className={`${baseClass}__editView`}>
|
||||
<DrawerHeader
|
||||
onClose={() => openModal(discardBulkUploadModalSlug)}
|
||||
title={getTranslation(collection.labels.singular, i18n)}
|
||||
title={getTranslation(collectionConfig.labels.singular, i18n)}
|
||||
/>
|
||||
{activeForm ? (
|
||||
<DocumentInfoProvider
|
||||
@@ -66,7 +66,7 @@ export function AddingFilesView() {
|
||||
Upload={documentSlots.Upload}
|
||||
versionCount={0}
|
||||
>
|
||||
<ActionsBar />
|
||||
<ActionsBar collectionConfig={collectionConfig} />
|
||||
<EditForm submitted={hasSubmitted} />
|
||||
</DocumentInfoProvider>
|
||||
) : null}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
'use client'
|
||||
|
||||
import type { ClientCollectionConfig } from 'payload'
|
||||
|
||||
import { useRouter, useSearchParams } from 'next/navigation.js'
|
||||
import React, { useCallback, useEffect } from 'react'
|
||||
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
'use client'
|
||||
|
||||
import type { ClientCollectionConfig, FieldWithPathClient } from 'payload'
|
||||
|
||||
import { useModal } from '@faceless-ui/modal'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
|
||||
import type { FormProps } from '../../../forms/Form/index.js'
|
||||
import type { State } from '../FormsManager/reducer.js'
|
||||
|
||||
import { Button } from '../../../elements/Button/index.js'
|
||||
import { Form } from '../../../forms/Form/index.js'
|
||||
import { RenderFields } from '../../../forms/RenderFields/index.js'
|
||||
import { XIcon } from '../../../icons/X/index.js'
|
||||
import { useAuth } from '../../../providers/Auth/index.js'
|
||||
import { useTranslation } from '../../../providers/Translation/index.js'
|
||||
import { FieldSelect } from '../../FieldSelect/index.js'
|
||||
import { useFormsManager } from '../FormsManager/index.js'
|
||||
import { baseClass, type EditManyBulkUploadsProps } from './index.js'
|
||||
import './index.scss'
|
||||
|
||||
export const EditManyBulkUploadsDrawerContent: React.FC<
|
||||
{
|
||||
collection: ClientCollectionConfig
|
||||
drawerSlug: string
|
||||
forms: State['forms']
|
||||
} & EditManyBulkUploadsProps
|
||||
> = (props) => {
|
||||
const { collection: { slug, fields, labels: { plural } } = {}, drawerSlug, forms } = props
|
||||
|
||||
const { permissions } = useAuth()
|
||||
const { i18n, t } = useTranslation()
|
||||
const { closeModal } = useModal()
|
||||
const { bulkUpdateForm } = useFormsManager()
|
||||
|
||||
const [selectedFields, setSelectedFields] = useState<FieldWithPathClient[]>([])
|
||||
const collectionPermissions = permissions?.collections?.[slug]
|
||||
|
||||
const handleSubmit: FormProps['onSubmit'] = useCallback(
|
||||
(formState) => {
|
||||
const pairedData = selectedFields.reduce((acc, field) => {
|
||||
const { path } = field
|
||||
if (formState[path]) {
|
||||
acc[path] = formState[path].value
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
void bulkUpdateForm(pairedData, () => closeModal(drawerSlug))
|
||||
},
|
||||
[closeModal, drawerSlug, bulkUpdateForm, selectedFields],
|
||||
)
|
||||
|
||||
return (
|
||||
<div className={`${baseClass}__main`}>
|
||||
<div className={`${baseClass}__header`}>
|
||||
<h2 className={`${baseClass}__header__title`}>
|
||||
{t('general:editingLabel', {
|
||||
count: forms.length,
|
||||
label: getTranslation(plural, i18n),
|
||||
})}
|
||||
</h2>
|
||||
<button
|
||||
aria-label={t('general:close')}
|
||||
className={`${baseClass}__header__close`}
|
||||
id={`close-drawer__${drawerSlug}`}
|
||||
onClick={() => closeModal(drawerSlug)}
|
||||
type="button"
|
||||
>
|
||||
<XIcon />
|
||||
</button>
|
||||
</div>
|
||||
<Form className={`${baseClass}__form`} initialState={{}} onSubmit={handleSubmit}>
|
||||
<FieldSelect fields={fields} setSelected={setSelectedFields} />
|
||||
{selectedFields.length === 0 ? null : (
|
||||
<RenderFields
|
||||
fields={selectedFields}
|
||||
parentIndexPath=""
|
||||
parentPath=""
|
||||
parentSchemaPath={slug}
|
||||
permissions={collectionPermissions?.fields}
|
||||
readOnly={false}
|
||||
/>
|
||||
)}
|
||||
<div className={`${baseClass}__sidebar-wrap`}>
|
||||
<div className={`${baseClass}__sidebar`}>
|
||||
<div className={`${baseClass}__sidebar-sticky-wrap`}>
|
||||
<div className={`${baseClass}__document-actions`}>
|
||||
<Button type="submit">{t('general:applyChanges')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
201
packages/ui/src/elements/BulkUpload/EditMany/index.scss
Normal file
201
packages/ui/src/elements/BulkUpload/EditMany/index.scss
Normal file
@@ -0,0 +1,201 @@
|
||||
@import '../../../scss/styles.scss';
|
||||
|
||||
@layer payload-default {
|
||||
.edit-many-bulk-uploads {
|
||||
&__toggle {
|
||||
font-size: 1rem;
|
||||
line-height: base(1.2);
|
||||
display: inline-flex;
|
||||
background: var(--theme-elevation-150);
|
||||
color: var(--theme-elevation-800);
|
||||
border-radius: $style-radius-s;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
border: 0;
|
||||
padding: 0 base(0.4);
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
|
||||
&:active,
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--theme-elevation-100);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: var(--theme-elevation-100);
|
||||
}
|
||||
}
|
||||
|
||||
&__form {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&__main {
|
||||
width: calc(100% - #{base(15)});
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
margin-top: base(2.5);
|
||||
margin-bottom: base(1);
|
||||
width: 100%;
|
||||
|
||||
&__title {
|
||||
margin: 0;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
&__close {
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
width: base(1);
|
||||
height: base(1);
|
||||
|
||||
svg {
|
||||
width: base(2);
|
||||
height: base(2);
|
||||
position: relative;
|
||||
inset-inline-start: base(-0.5);
|
||||
top: base(-0.5);
|
||||
|
||||
.stroke {
|
||||
stroke-width: 2px;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__edit {
|
||||
padding-top: base(1);
|
||||
padding-bottom: base(2);
|
||||
flex-grow: 1;
|
||||
}
|
||||
[dir='rtl'] &__sidebar-wrap {
|
||||
left: 0;
|
||||
border-right: 1px solid var(--theme-elevation-100);
|
||||
right: auto;
|
||||
}
|
||||
|
||||
&__sidebar-wrap {
|
||||
position: fixed;
|
||||
width: base(15);
|
||||
height: 100%;
|
||||
top: 0;
|
||||
right: 0;
|
||||
overflow: visible;
|
||||
border-left: 1px solid var(--theme-elevation-100);
|
||||
}
|
||||
|
||||
&__sidebar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
&__sidebar-sticky-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
&__collection-actions,
|
||||
&__meta,
|
||||
&__sidebar-fields {
|
||||
[dir='ltr'] & {
|
||||
padding-left: base(1.5);
|
||||
}
|
||||
[dir='rtl'] & {
|
||||
padding-right: base(1.5);
|
||||
}
|
||||
}
|
||||
|
||||
&__document-actions {
|
||||
padding-right: $baseline;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: var(--z-nav);
|
||||
|
||||
> * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@include mid-break {
|
||||
@include blur-bg;
|
||||
}
|
||||
}
|
||||
|
||||
&__document-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: base(1);
|
||||
gap: base(0.5);
|
||||
|
||||
.form-submit {
|
||||
width: calc(50% - #{base(1)});
|
||||
|
||||
@include mid-break {
|
||||
width: auto;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
padding-left: base(0.5);
|
||||
padding-right: base(0.5);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include mid-break {
|
||||
&__main {
|
||||
width: 100%;
|
||||
min-height: initial;
|
||||
}
|
||||
|
||||
&__sidebar-wrap {
|
||||
position: static;
|
||||
width: 100%;
|
||||
height: initial;
|
||||
}
|
||||
|
||||
&__form {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&__edit {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
&__document-actions {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: auto;
|
||||
z-index: var(--z-nav);
|
||||
}
|
||||
|
||||
&__document-actions,
|
||||
&__sidebar-fields {
|
||||
padding-left: var(--gutter-h);
|
||||
padding-right: var(--gutter-h);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
58
packages/ui/src/elements/BulkUpload/EditMany/index.tsx
Normal file
58
packages/ui/src/elements/BulkUpload/EditMany/index.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
'use client'
|
||||
|
||||
import type { ClientCollectionConfig } from 'payload'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import { useAuth } from '../../../providers/Auth/index.js'
|
||||
import { EditDepthProvider } from '../../../providers/EditDepth/index.js'
|
||||
import { useTranslation } from '../../../providers/Translation/index.js'
|
||||
import { Drawer, DrawerToggler } from '../../Drawer/index.js'
|
||||
import { useFormsManager } from '../FormsManager/index.js'
|
||||
import './index.scss'
|
||||
import { EditManyBulkUploadsDrawerContent } from './DrawerContent.js'
|
||||
|
||||
export const baseClass = 'edit-many-bulk-uploads'
|
||||
|
||||
export type EditManyBulkUploadsProps = {
|
||||
readonly collection: ClientCollectionConfig
|
||||
}
|
||||
|
||||
export const EditManyBulkUploads: React.FC<EditManyBulkUploadsProps> = (props) => {
|
||||
const { collection: { slug } = {}, collection } = props
|
||||
|
||||
const { permissions } = useAuth()
|
||||
|
||||
const { t } = useTranslation()
|
||||
const { forms } = useFormsManager() // Access forms managed in bulk uploads
|
||||
|
||||
const collectionPermissions = permissions?.collections?.[slug]
|
||||
const hasUpdatePermission = collectionPermissions?.update
|
||||
|
||||
const drawerSlug = `edit-${slug}-bulk-uploads`
|
||||
|
||||
if (!hasUpdatePermission) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<DrawerToggler
|
||||
aria-label={t('general:editAll')}
|
||||
className={`${baseClass}__toggle`}
|
||||
slug={drawerSlug}
|
||||
>
|
||||
{t('general:editAll')}
|
||||
</DrawerToggler>
|
||||
<EditDepthProvider>
|
||||
<Drawer Header={null} slug={drawerSlug}>
|
||||
<EditManyBulkUploadsDrawerContent
|
||||
collection={collection}
|
||||
drawerSlug={drawerSlug}
|
||||
forms={forms}
|
||||
/>
|
||||
</Drawer>
|
||||
</EditDepthProvider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -185,9 +185,14 @@
|
||||
|
||||
&__header__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--base);
|
||||
}
|
||||
|
||||
&__header__addFile {
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
&__toggler {
|
||||
display: none;
|
||||
margin: 0;
|
||||
|
||||
@@ -16,9 +16,9 @@ import { Pill } from '../../Pill/index.js'
|
||||
import { ShimmerEffect } from '../../ShimmerEffect/index.js'
|
||||
import { Thumbnail } from '../../Thumbnail/index.js'
|
||||
import { Actions } from '../ActionsBar/index.js'
|
||||
import './index.scss'
|
||||
import { AddFilesView } from '../AddFilesView/index.js'
|
||||
import { useFormsManager } from '../FormsManager/index.js'
|
||||
import './index.scss'
|
||||
import { useBulkUpload } from '../index.js'
|
||||
|
||||
const addMoreFilesDrawerSlug = 'bulk-upload-drawer--add-more-files'
|
||||
@@ -88,8 +88,13 @@ export function FileSidebar() {
|
||||
</div>
|
||||
|
||||
<div className={`${baseClass}__header__actions`}>
|
||||
{typeof maxFiles === 'number' && totalFileCount < maxFiles ? (
|
||||
<Pill onClick={() => openModal(addMoreFilesDrawerSlug)}>{t('upload:addFile')}</Pill>
|
||||
{(typeof maxFiles === 'number' ? totalFileCount < maxFiles : true) ? (
|
||||
<Pill
|
||||
className={`${baseClass}__header__addFile`}
|
||||
onClick={() => openModal(addMoreFilesDrawerSlug)}
|
||||
>
|
||||
{t('upload:addFile')}
|
||||
</Pill>
|
||||
) : null}
|
||||
<Button
|
||||
buttonStyle="transparent"
|
||||
|
||||
@@ -25,6 +25,10 @@ import { formsManagementReducer } from './reducer.js'
|
||||
type FormsManagerContext = {
|
||||
readonly activeIndex: State['activeIndex']
|
||||
readonly addFiles: (filelist: FileList) => Promise<void>
|
||||
readonly bulkUpdateForm: (
|
||||
updatedFields: Record<string, unknown>,
|
||||
afterStateUpdate?: () => void,
|
||||
) => Promise<void>
|
||||
readonly collectionSlug: string
|
||||
readonly docPermissions?: SanitizedDocumentPermissions
|
||||
readonly documentSlots: DocumentSlots
|
||||
@@ -51,6 +55,7 @@ type FormsManagerContext = {
|
||||
const Context = React.createContext<FormsManagerContext>({
|
||||
activeIndex: 0,
|
||||
addFiles: () => Promise.resolve(),
|
||||
bulkUpdateForm: () => null,
|
||||
collectionSlug: '',
|
||||
docPermissions: undefined,
|
||||
documentSlots: {},
|
||||
@@ -300,51 +305,49 @@ export function FormsManagerProvider({ children }: FormsManagerProps) {
|
||||
}
|
||||
|
||||
// should expose some sort of helper for this
|
||||
if (json?.errors?.length) {
|
||||
const [fieldErrors, nonFieldErrors] = json.errors.reduce(
|
||||
([fieldErrs, nonFieldErrs], err) => {
|
||||
const newFieldErrs: any[] = []
|
||||
const newNonFieldErrs: any[] = []
|
||||
const [fieldErrors, nonFieldErrors] = (json?.errors || []).reduce(
|
||||
([fieldErrs, nonFieldErrs], err) => {
|
||||
const newFieldErrs: any[] = []
|
||||
const newNonFieldErrs: any[] = []
|
||||
|
||||
if (err?.message) {
|
||||
newNonFieldErrs.push(err)
|
||||
}
|
||||
|
||||
if (Array.isArray(err?.data?.errors)) {
|
||||
err.data?.errors.forEach((dataError) => {
|
||||
if (dataError?.path) {
|
||||
newFieldErrs.push(dataError)
|
||||
} else {
|
||||
newNonFieldErrs.push(dataError)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return [
|
||||
[...fieldErrs, ...newFieldErrs],
|
||||
[...nonFieldErrs, ...newNonFieldErrs],
|
||||
]
|
||||
},
|
||||
[[], []],
|
||||
)
|
||||
|
||||
currentForms[i] = {
|
||||
errorCount: fieldErrors.length,
|
||||
formState: fieldReducer(currentForms[i].formState, {
|
||||
type: 'ADD_SERVER_ERRORS',
|
||||
errors: fieldErrors,
|
||||
}),
|
||||
}
|
||||
|
||||
if (req.status === 413) {
|
||||
// file too large
|
||||
currentForms[i] = {
|
||||
...currentForms[i],
|
||||
errorCount: currentForms[i].errorCount + 1,
|
||||
if (err?.message) {
|
||||
newNonFieldErrs.push(err)
|
||||
}
|
||||
|
||||
toast.error(nonFieldErrors[0]?.message)
|
||||
if (Array.isArray(err?.data?.errors)) {
|
||||
err.data?.errors.forEach((dataError) => {
|
||||
if (dataError?.path) {
|
||||
newFieldErrs.push(dataError)
|
||||
} else {
|
||||
newNonFieldErrs.push(dataError)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return [
|
||||
[...fieldErrs, ...newFieldErrs],
|
||||
[...nonFieldErrs, ...newNonFieldErrs],
|
||||
]
|
||||
},
|
||||
[[], []],
|
||||
)
|
||||
|
||||
currentForms[i] = {
|
||||
errorCount: fieldErrors.length,
|
||||
formState: fieldReducer(currentForms[i].formState, {
|
||||
type: 'ADD_SERVER_ERRORS',
|
||||
errors: fieldErrors,
|
||||
}),
|
||||
}
|
||||
|
||||
if (req.status === 413) {
|
||||
// file too large
|
||||
currentForms[i] = {
|
||||
...currentForms[i],
|
||||
errorCount: currentForms[i].errorCount + 1,
|
||||
}
|
||||
|
||||
toast.error(nonFieldErrors[0]?.message)
|
||||
}
|
||||
} catch (_) {
|
||||
// swallow
|
||||
@@ -385,6 +388,53 @@ export function FormsManagerProvider({ children }: FormsManagerProps) {
|
||||
[actionURL, activeIndex, forms, onSuccess, t, closeModal, drawerSlug],
|
||||
)
|
||||
|
||||
const bulkUpdateForm = React.useCallback(
|
||||
async (updatedFields: Record<string, unknown>, afterStateUpdate?: () => void) => {
|
||||
for (let i = 0; i < forms.length; i++) {
|
||||
Object.entries(updatedFields).forEach(([path, value]) => {
|
||||
if (forms[i].formState[path]) {
|
||||
forms[i].formState[path].value = value
|
||||
|
||||
dispatch({
|
||||
type: 'UPDATE_FORM',
|
||||
errorCount: forms[i].errorCount,
|
||||
formState: forms[i].formState,
|
||||
index: i,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if (typeof afterStateUpdate === 'function') {
|
||||
afterStateUpdate()
|
||||
}
|
||||
|
||||
if (hasSubmitted) {
|
||||
const { state } = await getFormState({
|
||||
collectionSlug,
|
||||
docPermissions,
|
||||
docPreferences: null,
|
||||
formState: forms[i].formState,
|
||||
operation: 'create',
|
||||
schemaPath: collectionSlug,
|
||||
})
|
||||
|
||||
const newFormErrorCount = Object.values(state).reduce(
|
||||
(acc, value) => (value?.valid === false ? acc + 1 : acc),
|
||||
0,
|
||||
)
|
||||
|
||||
dispatch({
|
||||
type: 'UPDATE_FORM',
|
||||
errorCount: newFormErrorCount,
|
||||
formState: state,
|
||||
index: i,
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
[collectionSlug, docPermissions, forms, getFormState, hasSubmitted],
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!collectionSlug) {
|
||||
return
|
||||
@@ -425,6 +475,7 @@ export function FormsManagerProvider({ children }: FormsManagerProps) {
|
||||
value={{
|
||||
activeIndex: state.activeIndex,
|
||||
addFiles,
|
||||
bulkUpdateForm,
|
||||
collectionSlug,
|
||||
docPermissions,
|
||||
documentSlots,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { FormFieldWithoutComponents, FormState } from 'payload'
|
||||
import type { FormState } from 'payload'
|
||||
|
||||
export type State = {
|
||||
activeIndex: number
|
||||
@@ -15,6 +15,13 @@ type Action =
|
||||
index: number
|
||||
type: 'UPDATE_ERROR_COUNT'
|
||||
}
|
||||
| {
|
||||
errorCount: number
|
||||
formState: FormState
|
||||
index: number
|
||||
type: 'UPDATE_FORM'
|
||||
updatedFields?: Record<string, unknown>
|
||||
}
|
||||
| {
|
||||
files: FileList
|
||||
initialState: FormState | null
|
||||
@@ -99,6 +106,25 @@ export function formsManagementReducer(state: State, action: Action): State {
|
||||
totalErrorCount: state.forms.reduce((acc, form) => acc + form.errorCount, 0),
|
||||
}
|
||||
}
|
||||
case 'UPDATE_FORM': {
|
||||
const updatedForms = [...state.forms]
|
||||
updatedForms[action.index].errorCount = action.errorCount
|
||||
|
||||
// Merge the existing formState with the new formState
|
||||
updatedForms[action.index] = {
|
||||
...updatedForms[action.index],
|
||||
formState: {
|
||||
...updatedForms[action.index].formState,
|
||||
...action.formState,
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
forms: updatedForms,
|
||||
totalErrorCount: state.forms.reduce((acc, form) => acc + form.errorCount, 0),
|
||||
}
|
||||
}
|
||||
default: {
|
||||
return state
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@import '../../../scss/styles.scss';
|
||||
|
||||
@layer payload-default {
|
||||
.bulk-upload--drawer-header {
|
||||
display: flex;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import { DrawerCloseButton } from '../DrawerCloseButton/index.js'
|
||||
|
||||
Reference in New Issue
Block a user