SoftwareBrothers / adminjs

AdminJS is an admin panel for apps written in node.js
https://adminjs.co
MIT License
8.26k stars 665 forks source link

How get file from custom action? #852

Closed rageroot closed 2 years ago

rageroot commented 3 years ago

admin-bro: 3.3.1, @admin-bro/express: 3.1.0, @admin-bro/typeorm: 1.5.0-beta.4,

Hey! I use and enjoy your product successfully! Faced an incomprehensible problem - I can not load a file from a custom action. The essence of the problem is that there is an XLSX file that you need to get, parse and put the data into the database. For this I would like to make a custom action with type: resourse. image I managed to create a resource and write a component. I'm trying to use DropZone to upload a file. In a standard Edit action using custom components, this approach works, but in a custom action I am having problems. resource:

actions: {
        questionsImport: {
            actionType: 'resource',
            name: 'questionsImport',
            icon: 'DocumentImport',
            isVisible: true,
            handler: async (req, res, data) => {
                console.log('Hello##################################################');
                return { record: data.record.toJSON(data.currentAdmin)};
            },
            component: AdminBro.bundle('../../../processing/components/questionsImport/index'),
        },
    },

component:

const questionsImport: React.FC<BasePropertyProps> = ({ property, record, onChange }) => {
    const onUpload = (files: File[]) => {
        onChange({
            ...record,
            params: {
                questionsImportFile: files,
            },
        });
    };

    const mimeTypes = ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'];
    return (
        <Box as="form" flex flexGrow={1} flexDirection="column">
            <Label>Импорт</Label>
            <DropZone onChange={onUpload} validate={{ mimeTypes }} />
            <div style={{ height: 40 }} />
            <DrawerFooter>
                <Button variant="primary" size="lg" type="submit">
                    Save
                </Button>
            </DrawerFooter>
        </Box>
    );
};

image

1- if you use type: resourse, then the handler does not work, console.log does not print a message. If you use type: record, then the handler works, console.log is displayed. 2- browser console writes an error Uncaught TypeError: onChange is not a function image

3- I can't figure out how to make a form submit button. Am I taking the right approach?

Please tell me how can I get a file from a custom action with type: resource

rageroot commented 3 years ago

Was able to solve the problem with crutches. I still couldn't use a custom hook. The action always refers to the new hook, but that's better than nothing. My decision:

resource

actions: {
        questionsImport: {
            actionType: 'resource',
            name: 'questionsImport',
            icon: 'DocumentImport',
            isVisible: true,
            variant: 'primary',
            component: AdminBro.bundle('../../../processing/components/Quiz/questionsImport/index'),
        },
    },
    properties: {
        questionsImport: {
            type: 'mixed',
            components: {
                edit: AdminBro.bundle(
                    '../../../processing/components/Quiz/questionsImport/dropZoneForImport'
                ),
            },
        },

index.tsx

mport React from 'react';
import {
    Box,
    DrawerFooter,
} from '@admin-bro/design-system';
import Button from '@admin-bro/design-system/src/atoms/button/button';
import { useTranslation, useRecord, BasePropertyComponent } from 'admin-bro';
import { useHistory } from 'react-router';

const questionsImport = props => {
    const { record: initialRecord, resource, action } = props;

    const { record, handleChange, submit } = useRecord(initialRecord, resource.id);
    const history = useHistory();
    const { translateButton } = useTranslation();
    const handleSubmit = event => {
        history.push('/admin');
        submit().then(response => {});
        return true;
    };

    return (
        <Box as="form" onSubmit={handleSubmit}>
            <BasePropertyComponent
                where="edit"
                onChange={handleChange}
                property={resource.properties.questionsImport}
                resource={resource}
                record={record}
            />
            <div style={{ height: 40 }} />
            <DrawerFooter>
                <Button variant="primary" size="lg">
                    {translateButton('save', resource.id)}
                </Button>
            </DrawerFooter>
        </Box>
    );
};

export default questionsImport;

BasePropertyComponent can't do DropZone, so I had to reinvent the wheel.

dropZoneForImport

import React from 'react';
import { Box, DropZone, DropZoneProps,  Label } from '@admin-bro/design-system';

const dropZoneForImport = props => {
    const { onChange, record } = props;

    const handleDropZoneChange: DropZoneProps['onChange'] = files => {
        onChange({
            ...record,
            params: {
                ...record.params,
                fileForImportQuizQuestions: files[0],
            },
        });
    };

    const mimeTypes = ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'];
    return (
        <Box flex flexGrow={1} flexDirection="column">
            <Label>Импорт</Label>
            <DropZone onChange={handleDropZoneChange} validate={{ mimeTypes }} />
        </Box>
    );
};

export default dropZoneForImport;

action new

 after: async (response, request, context) => {
        // const { resource, record, images } = context as ILoadedContext;
        const { resource, h, currentAdmin, translateMessage, record } = context;

        record.errors = {};
        return {
            redirectUrl: h.resourceUrl({ resourceId: resource._decorated?.id() || resource.id() }),
            notice: {
                message: translateMessage('Вопросы импортированы', resource.id()),
                type: 'success',
            },
            record: record.toJSON(currentAdmin),
        };
    },
};

redirect from adminBro new.tsx dont work. Rather, there is a redirect and a second later a redirect to the same custom action. Therefore, I made a redirect without waiting for a promise. new.tsx

const submit = (event: React.FormEvent<HTMLFormElement>): boolean => {
    event.preventDefault()
    handleSubmit().then((response) => {
      if (response.data.redirectUrl) {
        history.push(appendForceRefresh(response.data.redirectUrl))
      }
      // if record has id === has been created
      if (response.data.record.id) {
        handleChange({ params: {}, populated: {}, errors: {} } as RecordJSON)
      }
    })
    return false
  }

But, anyway, I would like to know how the developers themselves would solve this problem.