Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/meditor 858 bulk UI #65

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 196 additions & 0 deletions packages/app/components/bulk-update-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import { bulkUpdateDocument } from 'documents/http'
import { useContext, useEffect, useState } from 'react'
import { Button, Modal } from 'react-bootstrap'
import { AppContext } from './app-store'
import { JSONPatchOperation } from './json-patch-operation'

type Props = {
modelName: string
model: any
documents: any
onSubmit: any
}

type FormField = {
fieldName: string
op: 'add' | 'remove' | 'replace' | 'move' | 'copy' | 'test'
path: string
value: string
}
const BulkUpdateFormModal = (props: any) => {
const { modelName, model, documents, show, onComplete, onClose, user } = props

console.log(documents)

const [fields, setFields] = useState<FormField[]>([
{
fieldName: 'operation-item-1',
op: 'replace',
path: '',
value: '',
},
])

const [schemaProps, setSchemaProps] = useState<string[]>([])

const [showModal, setShowModal] = useState(show)

const { setSuccessNotification, setErrorNotification } = useContext(AppContext)

const addDynamicJSONPatchForm = () => {
setFields([
...fields,
{
fieldName: `operation-item-${fields.length + 1}`,
op: 'replace',
path: '',
value: '',
},
])
}

const removeDynamicJSONPatchForm = (fieldName: string) => {
const data = fields.filter(field => field.fieldName !== fieldName)

setFields(data)
}

const extractJSONFormSchemaProperties = (schema: any, prefix: string = '') => {
let properties: any = []

for (let key in schema.properties) {
let property = schema.properties[key]
let fullPath = prefix ? `${prefix}.${key}` : key

properties.push(fullPath)

if (property.type === 'object' && property.properties) {
properties = properties.concat(
extractJSONFormSchemaProperties(property, fullPath)
)
}
}

return properties
}

const updateOperations = (input: FormField) => {
console.log(input)

const data = fields.map(field => {
if (field.fieldName === input.fieldName) {
return input
}

return field
})

setFields(data)
}

useEffect(() => {
const schema = JSON.parse(model.schema)

console.log('schema', schema)
console.log('model', model)

let DocumentProperties = extractJSONFormSchemaProperties(schema)

if (DocumentProperties.includes(model.titleProperty)) {
DocumentProperties = DocumentProperties.filter(
property => model.titleProperty !== property
)
}

console.log('list of properties', DocumentProperties)

setSchemaProps(DocumentProperties)
}, [])

const onSubmit = async () => {
const operation = fields.map(item => {
const newItem = { ...item }
delete newItem['fieldName']

return newItem
})

console.log('operation: ', operation)

try {
const [error, response] = await bulkUpdateDocument(
modelName,
documents,
operation
)

const responseErrors = response
.map(item => {
if (item.status !== 200) {
return item.error
}
})
.filter(Boolean) // Filter out any undefined errors

console.log(responseErrors)

console.log(responseErrors.join('\n'))

if (error) {
setErrorNotification(
error.message || 'Failed to bulk update documents'
)
} else if (responseErrors.length > 0) {
setErrorNotification(responseErrors.join('\n'))
} else {
setSuccessNotification(
`${documents.length} Documents updated successfully!`
)
onClose(false) //close the model here
}
} catch (error) {
console.error(error)
setErrorNotification('An unexpected error occurred.')
}
}

return (
<Modal show={show} backdrop="static" size="lg" keyboard={false} centered>
<Modal.Header>
<Modal.Title>Bulk Update</Modal.Title>
</Modal.Header>

<Modal.Body>
<div className="mb-3">
Number of bulk items to update: {documents.length}
</div>
{fields.map((field, index) => (
<JSONPatchOperation
key={field.fieldName}
properties={schemaProps}
remove={removeDynamicJSONPatchForm}
field={field}
updateOperations={updateOperations}
model={model}
></JSONPatchOperation>
))}

<Button onClick={addDynamicJSONPatchForm}>Add Operation</Button>
</Modal.Body>

<Modal.Footer>
<Button onClick={onSubmit}>Submit</Button>

<Button
onClick={() => {
onClose(false)
}}
>
Cancel
</Button>
</Modal.Footer>
</Modal>
)
}

export { BulkUpdateFormModal }
194 changes: 194 additions & 0 deletions packages/app/components/json-patch-operation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import dynamic from 'next/dynamic'
import { useEffect, useState } from 'react'
import { Button, Col, Row } from 'react-bootstrap'
import Form from 'react-bootstrap/Form'
import { BsXCircleFill } from 'react-icons/bs'

type FormField = {
fieldName: string
op: string
path: string
value: string
}

type Props = {
field: FormField
properties: string[]
remove: any
updateOperations: any
model: any
}

// JsonSchemaForm widgets rely heavily on global window, so we'll need to load them in separately
// as the server side doesn't have a window!
const JsonSchemaForm = dynamic(() => import('./jsonschemaform/jsonschemaform'), {
ssr: false,
})

const JSONPatchOperation = (props: Props) => {
const { field, properties, remove, updateOperations, model } = props
const [operation, setOperation] = useState('replace')
const [path, setPath] = useState('')
const [pathValue, setPathValue] = useState('')
const [formData, setFormData] = useState({})
const [validationErrors, setValidationErrors] = useState([])

const schema = JSON.parse(model.schema)
// Initial schema template
const [initialSchema, setInitialSchema] = useState({})

// Validation function
const validateAgainstSchema = (data: any, schema: any) => {
const errors: string[] = []

for (const key in schema.properties) {
const propertySchema = schema.properties[key]
const value = data[key]

if (propertySchema.required && value === undefined) {
errors.push(`${key} is required`)
}

if (propertySchema.type && typeof value !== propertySchema.type) {
errors.push(`${key} should be of type ${propertySchema.type}`)
}
}

return errors
}

useEffect(() => {
updateOperations({
...field,
op: operation,
path: path,
value: pathValue,
})
}, [operation, path, pathValue])

//initialize schema on mount
useEffect(() => {
if (properties.length > 0) {
const defaultproperty = properties[0]
handlePropertySelect({ target: { value: defaultproperty } })
}
}, [properties])

const handlePropertySelect = (e: any) => {
const property = e.target.value
console.log(property)

const separatedProperty = property.split('.')

let newSchema = null
separatedProperty.forEach(item => {
if (newSchema) {
newSchema = newSchema.properties[item]
} else {
newSchema = schema.properties[item]
}
})

console.log('newSchema: ', newSchema)

setInitialSchema({
type: 'object',
properties: {
pathValue: newSchema,
},
definitions: schema.definitions,
})

setFormData({})

setPath(property)
}

// Handle form data change to update pathValue in the parent
const handleFormDataChange = (data: any) => {
if (data && data.formData.pathValue !== pathValue) {
setPathValue(data.formData.pathValue)
const errors = validateAgainstSchema(data.formData, initialSchema)
setValidationErrors(errors)
}
}

return (
<>
<Row>
<Col>
<Form.Group>
<Form.Label>Operation</Form.Label>
<Form.Control
as="select"
onChange={(e: any) => {
setOperation(e.target.value)
}}
>
<option value="replace">replace</option>
<option value="remove">delete</option>
<option value="add">add</option>
<option value="move">move</option>
<option value="copy">copy</option>
<option value="test">test</option>
</Form.Control>
</Form.Group>
</Col>

<Col>
<Form.Group>
<Form.Label>List of Properties</Form.Label>
<Form.Control as="select" onChange={handlePropertySelect}>
{properties.map((property, index) => (
<option key={property} value={property}>
{property}
</option>
))}
</Form.Control>
</Form.Group>
</Col>

{/* {operation !== 'remove' && (
<Col>
<Form.Group>
<Form.Label>Value</Form.Label>
<Form.Control
as="input"
onBlur={(e: any) => {
setPathValue(e.target.value)
}}
></Form.Control>
</Form.Group>
</Col>
)} */}

<Col md={2} className="d-flex align-items-center">
<Button variant="light" onClick={() => remove(field.fieldName)}>
<BsXCircleFill />
</Button>
</Col>
</Row>
{validationErrors.length > 0 && (
<div className="alert alert-danger">
{validationErrors.map(error => [error])}
</div>
)}
{path && (
<Row>
<Col>
<Form.Group>
<JsonSchemaForm
schema={initialSchema}
formData={formData}
layout={model.layout}
onChange={handleFormDataChange}
></JsonSchemaForm>
</Form.Group>
</Col>
</Row>
)}
</>
)
}

export { JSONPatchOperation }
Loading