Skip to content

Commit

Permalink
feat: proof of concept csv importing
Browse files Browse the repository at this point in the history
  • Loading branch information
Arcath committed Oct 15, 2024
1 parent 2ae0fdd commit b87b4b4
Show file tree
Hide file tree
Showing 4 changed files with 248 additions and 0 deletions.
223 changes: 223 additions & 0 deletions app/routes/app.$assetslug.import.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import {
type LoaderFunctionArgs,
type ActionFunctionArgs,
type MetaFunction,
json,
redirect,

Check failure on line 6 in app/routes/app.$assetslug.import.tsx

View workflow job for this annotation

GitHub Actions / ⬣ ESLint

'redirect' is defined but never used
unstable_parseMultipartFormData

Check failure on line 7 in app/routes/app.$assetslug.import.tsx

View workflow job for this annotation

GitHub Actions / ⬣ ESLint

'unstable_parseMultipartFormData' is defined but never used
} from '@remix-run/node'
import {useLoaderData, useActionData} from '@remix-run/react'

Check failure on line 9 in app/routes/app.$assetslug.import.tsx

View workflow job for this annotation

GitHub Actions / ⬣ ESLint

'useActionData' is defined but never used
import {asyncForEach, asyncMap, indexedBy} from '@arcath/utils'

Check failure on line 10 in app/routes/app.$assetslug.import.tsx

View workflow job for this annotation

GitHub Actions / ⬣ ESLint

'indexedBy' is defined but never used
import {getUniqueCountForAssetField} from '@prisma/client/sql'

Check failure on line 11 in app/routes/app.$assetslug.import.tsx

View workflow job for this annotation

GitHub Actions / ⬣ ESLint

'getUniqueCountForAssetField' is defined but never used
import {useState} from 'react'
import Papa from 'papaparse'

import {ensureUser} from '~/lib/utils/ensure-user'
import {getPrisma} from '~/lib/prisma.server'
import {FIELDS} from '~/lib/fields/field'

Check failure on line 17 in app/routes/app.$assetslug.import.tsx

View workflow job for this annotation

GitHub Actions / ⬣ ESLint

'FIELDS' is defined but never used
import {Button} from '~/lib/components/button'
import {pageTitle} from '~/lib/utils/page-title'
import {getUploadHandler} from '~/lib/utils/upload-handler.server'

Check failure on line 20 in app/routes/app.$assetslug.import.tsx

View workflow job for this annotation

GitHub Actions / ⬣ ESLint

'getUploadHandler' is defined but never used
import {FIELD_HANDLERS} from '~/lib/fields/field.server'

Check failure on line 21 in app/routes/app.$assetslug.import.tsx

View workflow job for this annotation

GitHub Actions / ⬣ ESLint

'FIELD_HANDLERS' is defined but never used
import {Input, Select} from '~/lib/components/input'

export const loader = async ({request, params}: LoaderFunctionArgs) => {
const user = await ensureUser(request, 'asset:view', {
assetSlug: params.assetslug
})

const prisma = getPrisma()

const asset = await prisma.asset.findFirstOrThrow({
where: {slug: params.assetslug},
include: {assetFields: {include: {field: true}, orderBy: {order: 'asc'}}}
})

return json({user, asset})
}

export const action = async ({request, params}: ActionFunctionArgs) => {
const user = await ensureUser(request, 'asset:write', {
assetSlug: params.assetslug
})

const prisma = getPrisma()

const asset = await prisma.asset.findFirstOrThrow({
where: {slug: params.assetslug},
include: {assetFields: {include: {field: true}, orderBy: {order: 'asc'}}}
})

const reader = request.body?.getReader()

const data = await reader?.read()

const {record, columnMappings} = JSON.parse(data!.value!.toString()) as {
record: string[]
columnMappings: string[]
}

const entry = await prisma.entry.create({
data: {assetId: asset.id, aclId: asset.aclId}
})

await asyncMap(
record,
async (value, i): Promise<{error: string; field: string} | boolean> => {
if (!columnMappings[i]) {
return false
}

await prisma.value.create({
data: {
entryId: entry.id,
fieldId: columnMappings[i],
value,
lastEditedById: user.id
}
})

return true
}
)

return json({status: 200})
}

export const meta: MetaFunction<typeof loader> = ({data}) => {
return [
{
title: pageTitle(data!.asset.singular, 'New')
}
]
}

const AssetImport = () => {
const [stage, setStage] = useState(1)
const [csvFile, setCsvFile] = useState<undefined | File>(undefined)
const [csvDetails, setCsvDetails] = useState<Array<any>>([])

Check failure on line 98 in app/routes/app.$assetslug.import.tsx

View workflow job for this annotation

GitHub Actions / ⬣ ESLint

Unexpected any. Specify a different type
const [columnMappings, setColumnMappings] = useState<string[]>([])
const [importedCount, setImportedCount] = useState(0)
const {asset} = useLoaderData<typeof loader>()

//const actionData = useActionData<typeof action>()

//const fields = indexedBy('fieldId', asset.assetFields)

switch (stage) {
default:
case 1:
return (
<div className="grid grid-cols-4 gap-4">
<div className="entry">
<h2>CSV Importer</h2>
<Input
type="file"
onChange={e => {
setCsvFile(e.target.files![0])
}}
/>
<Button
className="bg-success"
disabled={typeof csvFile === 'undefined'}
onClick={() => {
Papa.parse(csvFile!, {
complete: results => {
setCsvDetails(results.data)
setStage(2)
}
})
}}
>
Import
</Button>
</div>
</div>
)
break
case 2:
return (
<div className="grid grid-cols-4 gap-4">
<div className="entry">
<h2>CSV Importer</h2>
<p>Import file: {csvFile?.name}</p>
</div>
<div className="entry col-span-2">
<h2>Column Matcher</h2>
<table>
<thead>
<tr>
{csvDetails[0].map((v, i) => {

Check failure on line 150 in app/routes/app.$assetslug.import.tsx

View workflow job for this annotation

GitHub Actions / ʦ TypeScript

Parameter 'v' implicitly has an 'any' type.

Check failure on line 150 in app/routes/app.$assetslug.import.tsx

View workflow job for this annotation

GitHub Actions / ʦ TypeScript

Parameter 'i' implicitly has an 'any' type.
return <td key={i}>{v}</td>
})}
</tr>
</thead>
<tbody>
<tr>
{csvDetails[0].map((v, i) => {

Check failure on line 157 in app/routes/app.$assetslug.import.tsx

View workflow job for this annotation

GitHub Actions / ʦ TypeScript

Parameter 'v' implicitly has an 'any' type.

Check failure on line 157 in app/routes/app.$assetslug.import.tsx

View workflow job for this annotation

GitHub Actions / ʦ TypeScript

Parameter 'i' implicitly has an 'any' type.
return (
<td key={i}>
<Select
onChange={e => {
columnMappings[i] = e.target.value
setColumnMappings([...columnMappings])
}}
>
<option value={-1}>Do not import</option>
{asset.assetFields.map(({field}) => {
return (
<option key={field.id} value={field.id}>
{field.name}
</option>
)
})}
</Select>
</td>
)
})}
</tr>
</tbody>
</table>
<Button
onClick={() => {
setStage(3)
setImportedCount(0)
asyncForEach(csvDetails, async (v, i) => {
if (i === 0) {
return
}

await fetch(`/app/${asset.slug}/import`, {
method: 'POST',
body: JSON.stringify({record: v, columnMappings})
})

setImportedCount(importedCount + 1)
})
}}
>
Import
</Button>
</div>
</div>
)
break
case 3:
return (
<div className="grid grid-cols-4 gap-4">
<div className="entry">
<h2>CSV Importer</h2>
<p>Import file: {csvFile?.name}</p>
</div>
<div className="entry col-span-2">Importing...</div>
<div className="entry">
<h2>Imported</h2>
{importedCount} / {csvDetails.length - 1}
</div>
</div>
)
break
}
}

export default AssetImport
5 changes: 5 additions & 0 deletions app/routes/app.$assetslug.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ const Asset = () => {
link: `/app/${asset.slug}/add`,
label: `Add ${asset.singular}`,
className: 'bg-success'
},
{
link: `/app/${asset.slug}/import`,
label: `Import ${asset.plural}`,
className: 'bg-info'
}
]
case 'routes/app.$assetslug.$entry._index':
Expand Down
18 changes: 18 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"node-cron": "^3.0.3",
"npm-run-all": "^4.1.5",
"nprogress": "^0.2.0",
"papaparse": "^5.4.1",
"qrcode": "^1.5.4",
"react": "^18.3.1",
"react-dom": "^18.3.1",
Expand All @@ -55,6 +56,7 @@
"@remix-run/dev": "^2.13.1",
"@types/bcrypt": "^5.0.2",
"@types/nprogress": "^0.2.3",
"@types/papaparse": "^5.3.14",
"@types/qrcode": "^1.5.5",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.1",
Expand Down

0 comments on commit b87b4b4

Please sign in to comment.