Skip to content

Commit 122b5f4

Browse files
committed
react-dropzone による csv 取り込みを試してみる
1 parent 7933667 commit 122b5f4

File tree

4 files changed

+86
-1
lines changed

4 files changed

+86
-1
lines changed

sample/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"react": "^18.3.1",
2525
"react-admin": "^5.1.1",
2626
"react-dom": "^18.3.1",
27+
"react-dropzone": "^14.2.3",
2728
"react-router-dom": "^6.24.1",
2829
"zod": "^3.23.8",
2930
"zustand": "^4.5.4"
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import Dropzone from '@/components/organisms/products/dropzone'
2+
import type { Meta, StoryObj } from '@storybook/react'
3+
import { useState } from 'react'
4+
5+
const meta: Meta<typeof Dropzone> = {
6+
title: 'organisms/products/dropzone',
7+
component: Dropzone,
8+
parameters: {
9+
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
10+
layout: 'centered',
11+
},
12+
tags: ['autodocs'],
13+
}
14+
15+
export default meta
16+
17+
type Story = StoryObj<typeof Dropzone>
18+
19+
/**
20+
* 一括登録ボタン
21+
*/
22+
export const Default: Story = {
23+
render: () => {
24+
const [file, setFile] = useState<File | null>(null)
25+
return <Dropzone file={file} setFile={setFile} />
26+
},
27+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React, { useCallback } from 'react'
2+
import { useDropzone } from 'react-dropzone'
3+
4+
type Props = {
5+
file: File | null
6+
setFile: (file: File) => void
7+
}
8+
9+
const DropZone = React.memo(function DropZone({ file, setFile }: Props) {
10+
const onDrop = useCallback(
11+
async (acceptedFiles: File[]) => {
12+
const importFile = acceptedFiles[0]
13+
if (!importFile) return
14+
15+
setFile(importFile)
16+
},
17+
[setFile],
18+
)
19+
const {
20+
getRootProps,
21+
getInputProps,
22+
isDragAccept,
23+
isDragReject,
24+
isDragActive,
25+
} = useDropzone({
26+
onDrop,
27+
accept: {
28+
'text/csv': ['.csv'],
29+
},
30+
maxFiles: 1,
31+
})
32+
const message = useCallback(() => {
33+
if (file) return `${file.name} が読み込まれています`
34+
35+
if (isDragReject)
36+
return 'ファイル形式が不正、もしくは複数のファイル取り込もうとしています。'
37+
38+
if (isDragActive && isDragAccept) return '読み込み可能な形式です'
39+
40+
return 'ファイルをここに D&D するか、クリックしてファイルを選択してください'
41+
}, [file, isDragReject, isDragActive, isDragAccept])
42+
43+
return (
44+
<div
45+
{...getRootProps()}
46+
className={`w-full min-h-56 border-dashed border-gray-800 border-2 flex items-center rounded-3xl justify-center ${file ? 'bg-green-200' : ''}`}
47+
>
48+
<input {...getInputProps()} />
49+
<p className="text-center">{message()}</p>
50+
</div>
51+
)
52+
})
53+
54+
export default DropZone

sample/src/pages/product-list.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ import ProductDeletionForm from '@/components/molecules/forms/products/product-d
55
import ProductEditingForm from '@/components/molecules/forms/products/product-editing-form'
66
import Modal from '@/components/molecules/modals/modal'
77
import BulkImportButton from '@/components/organisms/products/bulk-import-button'
8+
import DropZone from '@/components/organisms/products/dropzone'
89
import TenantsTemplate from '@/components/templates/tenants-template'
910
import { useProducts } from '@/hooks/use-products'
1011
import { getProductsAndRefresh } from '@/reducks/products/selectors'
1112
import { Product } from '@/reducks/products/types'
1213
import { css } from '@emotion/react'
13-
import { useEffect, useState } from 'react'
14+
import React, { useEffect, useState } from 'react'
1415

1516
const styles = {
1617
button: css`
@@ -45,6 +46,7 @@ const ProductList: React.FC = () => {
4546
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false)
4647
const [currentProduct, setCurrentProduct] = useState<Product | null>(null)
4748
const [productToDelete, setProductToDelete] = useState<Product | null>(null)
49+
const [file, setFile] = useState<File | null>(null)
4850

4951
useEffect(() => {
5052
refresh()
@@ -57,6 +59,7 @@ const ProductList: React.FC = () => {
5759
新規作成
5860
</button>
5961
<BulkImportButton />
62+
<DropZone file={file} setFile={setFile} />
6063

6164
{isDeleteDialogOpen && productToDelete && (
6265
<div css={styles.overlay}>

0 commit comments

Comments
 (0)