1- import React , { useState , useEffect } from 'react' ;
1+ import React , { useState , useEffect , useRef } from 'react' ;
22import { graphql , usePreloadedQuery , useMutation , useQueryLoader } from 'react-relay' ;
33import type { SupplierListQuery } from '../__generated__/SupplierListQuery.graphql.ts' ;
44
@@ -57,6 +57,27 @@ const deleteSupplierMutation = graphql`
5757 }
5858` ;
5959
60+ // Define the bulk import suppliers mutation
61+ const importSuppliersMutation = graphql `
62+ mutation SupplierListImportMutation($suppliers: [CreateSupplierInput!]!) {
63+ importSuppliers(suppliers: $suppliers) {
64+ id
65+ name
66+ address
67+ city
68+ state
69+ zipCode
70+ country
71+ contactName
72+ contactEmail
73+ contactPhone
74+ website
75+ createdAt
76+ updatedAt
77+ }
78+ }
79+ ` ;
80+
6081interface Supplier {
6182 id : string ;
6283 name : string ;
@@ -95,6 +116,8 @@ const SupplierListContent = ({
95116 loadQuery : ( variables : { page : number ; pageSize : number } , options ?: { fetchPolicy ?: string } ) => void ;
96117} ) => {
97118 const [ showCreateForm , setShowCreateForm ] = useState < boolean > ( false ) ;
119+ const [ isImportPending , setIsImportPending ] = useState < boolean > ( false ) ;
120+ const fileInputRef = useRef < HTMLInputElement > ( null ) ;
98121 const [ createForm , setCreateForm ] = useState < CreateSupplierInput > ( {
99122 name : '' ,
100123 address : '' ,
@@ -112,6 +135,7 @@ const SupplierListContent = ({
112135
113136 const [ createSupplier , isCreatePending ] = useMutation ( createSupplierMutation ) ;
114137 const [ deleteSupplier , isDeletePending ] = useMutation ( deleteSupplierMutation ) ;
138+ const [ importSuppliers ] = useMutation ( importSuppliersMutation ) ;
115139
116140 const suppliers = data . suppliers ?. items || [ ] ;
117141 const totalItems = data . suppliers ?. totalItems || 0 ;
@@ -188,6 +212,149 @@ const SupplierListContent = ({
188212 } ) ;
189213 } ;
190214
215+ const parseCSV = ( csvText : string ) : CreateSupplierInput [ ] => {
216+ const lines = csvText . trim ( ) . split ( '\n' ) ;
217+ if ( lines . length < 2 ) {
218+ throw new Error ( 'CSV must have header row and at least one data row' ) ;
219+ }
220+
221+ const headers = lines [ 0 ] . split ( ',' ) . map ( h => h . replace ( / " / g, '' ) . trim ( ) ) ;
222+ const suppliers : CreateSupplierInput [ ] = [ ] ;
223+
224+ for ( let i = 1 ; i < lines . length ; i ++ ) {
225+ const line = lines [ i ] ;
226+ if ( line . trim ( ) === '' ) continue ;
227+
228+ // Simple CSV parsing (handles quoted fields)
229+ const values : string [ ] = [ ] ;
230+ let currentValue = '' ;
231+ let inQuotes = false ;
232+
233+ for ( let j = 0 ; j < line . length ; j ++ ) {
234+ const char = line [ j ] ;
235+ if ( char === '"' ) {
236+ inQuotes = ! inQuotes ;
237+ } else if ( char === ',' && ! inQuotes ) {
238+ values . push ( currentValue . trim ( ) ) ;
239+ currentValue = '' ;
240+ } else {
241+ currentValue += char ;
242+ }
243+ }
244+ values . push ( currentValue . trim ( ) ) ; // Push the last value
245+
246+ const supplier : CreateSupplierInput = {
247+ name : '' ,
248+ address : '' ,
249+ city : '' ,
250+ state : '' ,
251+ zipCode : '' ,
252+ country : '' ,
253+ contactName : '' ,
254+ contactEmail : '' ,
255+ contactPhone : '' ,
256+ website : ''
257+ } ;
258+
259+ // Map CSV columns to supplier fields
260+ headers . forEach ( ( header , index ) => {
261+ const value = values [ index ] || '' ;
262+ switch ( header . toLowerCase ( ) ) {
263+ case 'name' :
264+ supplier . name = value ;
265+ break ;
266+ case 'address' :
267+ supplier . address = value ;
268+ break ;
269+ case 'city' :
270+ supplier . city = value ;
271+ break ;
272+ case 'state' :
273+ supplier . state = value ;
274+ break ;
275+ case 'zipcode' :
276+ case 'zip_code' :
277+ supplier . zipCode = value ;
278+ break ;
279+ case 'country' :
280+ supplier . country = value ;
281+ break ;
282+ case 'contactname' :
283+ case 'contact_name' :
284+ supplier . contactName = value ;
285+ break ;
286+ case 'contactemail' :
287+ case 'contact_email' :
288+ supplier . contactEmail = value ;
289+ break ;
290+ case 'contactphone' :
291+ case 'contact_phone' :
292+ supplier . contactPhone = value ;
293+ break ;
294+ case 'website' :
295+ supplier . website = value || undefined ;
296+ break ;
297+ }
298+ } ) ;
299+
300+ // Validate required fields
301+ if ( supplier . name && supplier . contactEmail ) {
302+ suppliers . push ( supplier ) ;
303+ }
304+ }
305+
306+ return suppliers ;
307+ } ;
308+
309+ const handleFileUpload = async ( event : React . ChangeEvent < HTMLInputElement > ) => {
310+ const file = event . target . files ?. [ 0 ] ;
311+ if ( ! file ) return ;
312+
313+ if ( ! file . name . toLowerCase ( ) . endsWith ( '.csv' ) ) {
314+ alert ( 'Please select a CSV file' ) ;
315+ return ;
316+ }
317+
318+ setIsImportPending ( true ) ;
319+
320+ try {
321+ const text = await file . text ( ) ;
322+ const suppliersData = parseCSV ( text ) ;
323+
324+ if ( suppliersData . length === 0 ) {
325+ alert ( 'No valid supplier data found in CSV file' ) ;
326+ setIsImportPending ( false ) ;
327+ return ;
328+ }
329+
330+ importSuppliers ( {
331+ variables : { suppliers : suppliersData } ,
332+ onCompleted : ( response ) => {
333+ alert ( `Successfully imported ${ response . importSuppliers ?. length || 0 } suppliers` ) ;
334+ setIsImportPending ( false ) ;
335+ // Clear the file input
336+ if ( fileInputRef . current ) {
337+ fileInputRef . current . value = '' ;
338+ }
339+ // Refresh the list
340+ loadQuery (
341+ { page : 0 , pageSize : 10 } ,
342+ { fetchPolicy : 'network-only' }
343+ ) ;
344+ } ,
345+ onError : ( error ) => {
346+ console . error ( 'Error importing suppliers:' , error ) ;
347+ alert ( 'Error importing suppliers. Please check the file format and try again.' ) ;
348+ setIsImportPending ( false ) ;
349+ }
350+ } ) ;
351+ } catch ( error ) {
352+ console . error ( 'Error parsing CSV:' , error ) ;
353+ alert ( 'Error parsing CSV file. Please check the file format.' ) ;
354+ setIsImportPending ( false ) ;
355+ }
356+ } ;
357+
191358 return (
192359 < div style = { { display : 'flex' , flexFlow : 'column' , textAlign : 'left' } } >
193360 < h1 > Suppliers</ h1 >
@@ -196,10 +363,24 @@ const SupplierListContent = ({
196363 < button
197364 onClick = { ( ) => setShowCreateForm ( true ) }
198365 disabled = { showCreateForm }
199- style = { { marginBottom : '20px' } }
366+ style = { { marginBottom : '20px' , marginRight : '10px' } }
200367 >
201368 Add New Supplier
202369 </ button >
370+ < input
371+ type = "file"
372+ accept = ".csv"
373+ onChange = { handleFileUpload }
374+ style = { { display : 'none' } }
375+ ref = { fileInputRef }
376+ />
377+ < button
378+ onClick = { ( ) => fileInputRef . current ?. click ( ) }
379+ disabled = { isImportPending }
380+ style = { { marginBottom : '20px' } }
381+ >
382+ { isImportPending ? 'Importing...' : 'Import CSV' }
383+ </ button >
203384 </ div >
204385
205386 { showCreateForm && (
0 commit comments