| | |
| | | "@mui/x-tree-view": "^7.16.0", |
| | | "axios": "^1.7.4", |
| | | "date-fns": "^3.6.0", |
| | | "papaparse": "^5.4.1", |
| | | "pixi.js": "^7.4.0", |
| | | "react": "^18.3.0", |
| | | "react-admin": "^5.1.0", |
| | |
| | | "url": "https://github.com/sponsors/sindresorhus" |
| | | } |
| | | }, |
| | | "node_modules/papaparse": { |
| | | "version": "5.4.1", |
| | | "resolved": "https://registry.npmmirror.com/papaparse/-/papaparse-5.4.1.tgz", |
| | | "integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==" |
| | | }, |
| | | "node_modules/parent-module": { |
| | | "version": "1.0.1", |
| | | "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", |
| | |
| | | "axios": "^1.7.4", |
| | | "date-fns": "^3.6.0", |
| | | "pixi.js": "^7.4.0", |
| | | "papaparse": "^5.4.1", |
| | | "react": "^18.3.0", |
| | | "react-admin": "^5.1.0", |
| | | "react-dom": "^18.3.0", |
| | |
| | | import MyField from "../components/MyField"; |
| | | import { PAGE_DRAWER_WIDTH, OPERATE_MODE } from '@/config/setting'; |
| | | import * as Common from '@/utils/common'; |
| | | import { ImportButton } from './ImportButton' |
| | | import { ImportButton } from '../components/ImportButton' |
| | | import { useCodeImport } from './useCodeImport'; |
| | | |
| | | import * as sampleCsv from './importTemp.csv?raw'; |
| | | |
| | | const StyledDatagrid = styled(DatagridConfigurable)(({ theme }) => ({ |
| | | '& .css-1vooibu-MuiSvgIcon-root': { |
| | |
| | | <FilterButton /> |
| | | <MyCreateButton onClick={() => { setCreateDialog(true) }} /> |
| | | <SelectColumnsButton preferenceKey='code' /> |
| | | <ImportButton /> |
| | | <ImportButton sampleCsv={sampleCsv} useCodeImport={useCodeImport} /> |
| | | <MyExportButton /> |
| | | </TopToolbar> |
| | | )} |
New file |
| | |
| | | first_name,last_name,gender,title,company,email,phone_1_number,phone_1_type,phone_2_number,phone_2_type,background,first_seen,last_seen,has_newsletter,status,tags,linkedin_url |
| | | John,Doe,male,Sales Executive,Acme,john@doe.example,659-980-2015,work,740.645.3807,home,,2024-07-01,2024-07-01T11:54:49.950Z,FALSE,in-contract,"influencer, developer",https://www.linkedin.com/in/johndoe |
| | | Jane,Doe,female,Designer,Acme,jane@doe.example,659-980-2020,work,740.647.3802,home,,2024-07-01,2024-07-01T11:54:49.950Z,FALSE,in-contract,"UI, design",https://www.linkedin.com/in/janedoe |
| | | Camille,Brown,nonbinary,Accountant,Atomic Corp,person@doe.example,659-910-3010,work,740.698.3752,home,,2024-07-01,2024-07-01T11:54:49.950Z,FALSE,in-contract,"payroll, accountant",, |
New file |
| | |
| | | import { useCallback, useMemo } from 'react'; |
| | | import { useDataProvider, useGetIdentity } from 'react-admin'; |
| | | |
| | | export function useCodeImport() { |
| | | |
| | | const processBatch = useCallback(async (batch) => { |
| | | console.log(batch); |
| | | |
| | | }, []); |
| | | |
| | | return { |
| | | processBatch, |
| | | }; |
| | | } |
File was renamed from zy-acs-flow/src/page/code/ImportButton.jsx |
| | |
| | | import UploadIcon from '@mui/icons-material/Upload'; |
| | | import { useState } from 'react'; |
| | | import { Button } from 'react-admin'; |
| | | // import { ImportModal } from './ImportModal'; |
| | | import { ImportModal } from './ImportModal'; |
| | | |
| | | export const ImportButton = () => { |
| | | export const ImportButton = (props) => { |
| | | const [modalOpen, setModalOpen] = useState(false); |
| | | |
| | | const handleOpenModal = () => { |
| | |
| | | const handleCloseModal = () => { |
| | | setModalOpen(false); |
| | | }; |
| | | |
| | | |
| | | return ( |
| | | <> |
| | | <Button |
| | |
| | | onClick={handleOpenModal} |
| | | /> |
| | | |
| | | {/* <ImportModal open={modalOpen} onClose={handleCloseModal} /> */} |
| | | <ImportModal open={modalOpen} onClose={handleCloseModal} {...props} /> |
| | | </> |
| | | ); |
| | | }; |
File was renamed from zy-acs-flow/src/page/code/ImportModal.jsx |
| | |
| | | import { useEffect, useState } from 'react'; |
| | | import { Box, CircularProgress, Stack, Typography } from '@mui/material'; |
| | | import Alert from '@mui/material/Alert'; |
| | | import Dialog from '@mui/material/Dialog'; |
| | |
| | | useRefresh, |
| | | } from 'react-admin'; |
| | | import { Link } from 'react-router-dom'; |
| | | import { DialogCloseButton } from '../misc/DialogCloseButton'; |
| | | import { usePapaParse } from '../misc/usePapaParse'; |
| | | import { ContactImportSchema, useContactImport } from './useContactImport'; |
| | | import DialogCloseButton from './DialogCloseButton'; |
| | | import { usePapaParse } from './usePapaParse'; |
| | | |
| | | import { MouseEvent, useEffect, useState } from 'react'; |
| | | import * as sampleCsv from './contacts_export.csv?raw'; |
| | | |
| | | const SAMPLE_URL = `data:text/csv;name=crm_contacts_sample.csv;charset=utf-8,${encodeURIComponent(sampleCsv.default)}`; |
| | | |
| | | |
| | | export function ImportModal({ open, onClose }) { |
| | | export function ImportModal({ open, onClose, sampleCsv, useCodeImport }) { |
| | | const refresh = useRefresh(); |
| | | const { processBatch } = useContactImport(); |
| | | const { importer, parseCsv, reset } = usePapaParse<ContactImportSchema>({ |
| | | |
| | | const SAMPLE_URL = `data:text/csv;name=crm_contacts_sample.csv;charset=utf-8,${encodeURIComponent(sampleCsv.default)}`; |
| | | |
| | | const { processBatch } = useCodeImport(); |
| | | const { importer, parseCsv, reset } = usePapaParse({ |
| | | batchSize: 10, |
| | | processBatch, |
| | | }); |
New file |
| | |
| | | import * as Papa from 'papaparse'; |
| | | import { useCallback, useMemo, useRef, useState } from 'react'; |
| | | |
| | | export function usePapaParse({ batchSize = 10, processBatch }) { |
| | | const importIdRef = useRef(0); |
| | | |
| | | const [importer, setImporter] = useState({ |
| | | state: 'idle', |
| | | }); |
| | | |
| | | const reset = useCallback(() => { |
| | | setImporter({ |
| | | state: 'idle', |
| | | }); |
| | | importIdRef.current += 1; |
| | | }, []); |
| | | |
| | | const parseCsv = useCallback((file) => { |
| | | setImporter({ |
| | | state: 'parsing', |
| | | }); |
| | | |
| | | const importId = importIdRef.current; |
| | | Papa.parse(file, { |
| | | header: true, |
| | | skipEmptyLines: true, |
| | | async complete(results) { |
| | | if (importIdRef.current !== importId) { |
| | | return; |
| | | } |
| | | |
| | | setImporter({ |
| | | state: 'running', |
| | | rowCount: results.data.length, |
| | | errorCount: results.errors.length, |
| | | importCount: 0, |
| | | remainingTime: null, |
| | | }); |
| | | |
| | | let totalTime = 0; |
| | | for (let i = 0; i < results.data.length; i += batchSize) { |
| | | if (importIdRef.current !== importId) { |
| | | return; |
| | | } |
| | | |
| | | const batch = results.data.slice(i, i + batchSize); |
| | | try { |
| | | const start = Date.now(); |
| | | await processBatch(batch); |
| | | totalTime += Date.now() - start; |
| | | |
| | | const meanTime = totalTime / (i + batch.length); |
| | | setImporter(previous => { |
| | | if (previous.state === 'running') { |
| | | const importCount = |
| | | previous.importCount + batch.length; |
| | | return { |
| | | ...previous, |
| | | importCount, |
| | | remainingTime: |
| | | meanTime * |
| | | (results.data.length - importCount), |
| | | }; |
| | | } |
| | | return previous; |
| | | }); |
| | | } catch (error) { |
| | | console.error('Failed to import batch', error); |
| | | setImporter(previous => |
| | | previous.state === 'running' |
| | | ? { |
| | | ...previous, |
| | | errorCount: |
| | | previous.errorCount + |
| | | batch.length, |
| | | } |
| | | : previous |
| | | ); |
| | | } |
| | | } |
| | | |
| | | setImporter(previous => |
| | | previous.state === 'running' |
| | | ? { |
| | | ...previous, |
| | | state: 'complete', |
| | | remainingTime: null, |
| | | } |
| | | : previous |
| | | ); |
| | | }, |
| | | error(error) { |
| | | setImporter({ |
| | | state: 'error', |
| | | error, |
| | | }); |
| | | }, |
| | | dynamicTyping: true, |
| | | }); |
| | | }, [batchSize, processBatch]); |
| | | |
| | | return useMemo(() => ({ importer, parseCsv, reset, }), [importer, parseCsv, reset]); |
| | | } |