-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1127 from andrew-bierman/feat/import_items
Import Items for packs and global items
- Loading branch information
Showing
17 changed files
with
582 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import React, { useState, FC } from 'react'; | ||
import { View, Platform } from 'react-native'; | ||
import { DropdownComponent, RButton, RText } from '@packrat/ui'; | ||
import useTheme from '../../hooks/useTheme'; | ||
import * as DocumentPicker from 'expo-document-picker'; | ||
import { useAddPackItem } from 'app/hooks/packs/useAddPackItem'; | ||
import { useAddItem } from 'app/hooks/items'; | ||
import { useImportPackItem } from 'app/hooks/packs/useImportPackItem'; | ||
import { useImportItem } from 'app/hooks/items/useImportItem'; | ||
|
||
interface ImportFormProps { | ||
showSubmitButton?: boolean; | ||
closeModalHandler?: () => void; | ||
packId?: string; | ||
ownerId?: string; | ||
currentPack?: { | ||
items: Array<{ | ||
category: { | ||
name: string; | ||
}; | ||
}>; | ||
} | null; | ||
currentpage?: string; | ||
} | ||
|
||
interface SelectedType { | ||
label: string; | ||
value: string; | ||
} | ||
|
||
const data = [ | ||
{ label: 'CSV', value: '.csv', key: '.csv' }, | ||
{ label: 'Other', value: '*', key: '*' }, | ||
]; | ||
|
||
export const ImportForm: FC<ImportFormProps> = ({ | ||
packId, | ||
ownerId, | ||
closeModalHandler, | ||
currentpage, | ||
}) => { | ||
const { currentTheme } = useTheme(); | ||
const { addPackItem } = useAddPackItem(); | ||
const { handleAddNewItem } = useAddItem(); | ||
const { handleImportNewItems } = useImportItem(); | ||
const { importPackItem } = useImportPackItem(); | ||
|
||
const [selectedType, setSelectedType] = useState<SelectedType>({ | ||
label: 'CSV', | ||
value: '.csv', | ||
}); | ||
|
||
const handleSelectChange = (selectedValue: string) => { | ||
const newValue = data.find((item) => item.value === selectedValue); | ||
if (newValue) setSelectedType(newValue); | ||
}; | ||
|
||
const handleItemImport = async () => { | ||
try { | ||
const res = await DocumentPicker.getDocumentAsync({ | ||
type: [selectedType.value], | ||
}); | ||
|
||
if (res.canceled) { | ||
console.log('User canceled file picker'); | ||
return; | ||
} | ||
|
||
let fileContent; | ||
|
||
if (selectedType.value === '.csv') { | ||
if (Platform.OS === 'web') { | ||
if (res.assets && res.assets.length > 0) { | ||
const file = res.assets[0]; | ||
const base64Content = file.uri.split(',')[1]; | ||
fileContent = atob(base64Content); | ||
} else { | ||
throw new Error('No file content available'); | ||
} | ||
} else { | ||
const response = await fetch(res.uri); | ||
fileContent = await response.text(); | ||
} | ||
|
||
if (currentpage === 'items') { | ||
handleImportNewItems({ content: fileContent, ownerId }); | ||
} else { | ||
importPackItem({ content: fileContent, packId, ownerId }); | ||
} | ||
} | ||
} catch (err) { | ||
console.error('Error importing file:', err); | ||
} finally { | ||
closeModalHandler(); | ||
} | ||
}; | ||
|
||
return ( | ||
<View style={{ minWidth: 320 }}> | ||
<View | ||
style={{ | ||
flexDirection: 'row', | ||
justifyContent: 'space-between', | ||
width: '100%', | ||
marginBottom: 10, | ||
}} | ||
> | ||
<DropdownComponent | ||
value={selectedType} | ||
data={data} | ||
onValueChange={handleSelectChange} | ||
placeholder={`Select file type: ${selectedType.label}`} | ||
native={true} | ||
style={{ width: '100%' }} | ||
/> | ||
</View> | ||
<RButton onClick={handleItemImport}> | ||
<RText style={{ color: currentTheme.colors.text }}>Import Item</RText> | ||
</RButton> | ||
</View> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { View } from 'react-native'; | ||
import { ImportForm } from './ImportForm'; | ||
import { type Item } from '@packrat/validations'; | ||
import { useAuthUser } from 'app/auth/hooks'; | ||
|
||
interface ImportItemProps { | ||
packId: string; | ||
currentPack?: any; | ||
closeModalHandler?: () => void; | ||
setIsImportItemModalOpen?: (isOpen: boolean) => void; | ||
} | ||
|
||
type ImportItem = Omit<Item, 'id'> & { id: string }; | ||
|
||
export const ImportItem = ({ | ||
currentPack, | ||
closeModalHandler, | ||
packId, | ||
}: ImportItemProps) => { | ||
const user = useAuthUser(); | ||
const ownerId = user?.id; | ||
|
||
return ( | ||
<View> | ||
<ImportForm | ||
closeModalHandler={closeModalHandler} | ||
packId={packId} | ||
currentPack={currentPack} | ||
ownerId={ownerId} | ||
/> | ||
</View> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { View } from 'react-native'; | ||
import { useModal } from '@packrat/ui'; | ||
import { useAuthUser } from 'app/auth/hooks'; | ||
import { ImportForm } from './ImportForm'; | ||
|
||
export const ImportItemGlobal = () => { | ||
const authUser = useAuthUser(); | ||
const ownerId = authUser?.id; | ||
|
||
if (!authUser) { | ||
return null; // or some fallback | ||
} | ||
|
||
const { setIsModalOpen } = useModal(); | ||
|
||
const closeModalHandler = () => { | ||
setIsModalOpen(false); | ||
}; | ||
|
||
return ( | ||
<View> | ||
<ImportForm | ||
closeModalHandler={closeModalHandler} | ||
currentpage="items" | ||
ownerId={ownerId} | ||
/> | ||
</View> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { ImportItem } from '../item/ImportItem'; | ||
import { BaseModal } from '@packrat/ui'; | ||
|
||
interface ImportItemModalProps { | ||
currentPackId: string; | ||
currentPack: any; | ||
isImportItemModalOpen: boolean; | ||
setIsImportItemModalOpen: any; | ||
} | ||
|
||
export const ImportItemModal = ({ | ||
currentPackId, | ||
currentPack, | ||
isImportItemModalOpen, | ||
setIsImportItemModalOpen, | ||
}: ImportItemModalProps) => { | ||
return ( | ||
<BaseModal | ||
title="Import Item" | ||
trigger="Import Item" | ||
footerComponent={undefined} | ||
isOpen={isImportItemModalOpen} | ||
onOpen={() => setIsImportItemModalOpen(true)} | ||
onClose={() => setIsImportItemModalOpen(false)} | ||
> | ||
<ImportItem | ||
packId={currentPackId} | ||
currentPack={currentPack} | ||
closeModalHandler={() => setIsImportItemModalOpen(false)} | ||
/> | ||
</BaseModal> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { queryTrpc } from 'app/trpc'; | ||
import { useCallback } from 'react'; | ||
import { useOfflineQueue } from 'app/hooks/offline'; | ||
import { useItemsUpdater } from './useItemsUpdater'; | ||
|
||
interface State { | ||
items?: Array<{ id: string }>; | ||
} | ||
|
||
export const useImportItem = () => { | ||
const utils = queryTrpc.useContext(); | ||
const { mutate } = queryTrpc.importItemsGlobal.useMutation(); | ||
const { isConnected, addOfflineRequest } = useOfflineQueue(); | ||
const updateItems = useItemsUpdater(); | ||
|
||
const handleImportNewItems = useCallback( | ||
(newItem) => { | ||
if (isConnected) { | ||
return mutate(newItem, { | ||
onSuccess: () => { | ||
utils.getItemsGlobally.invalidate(); | ||
}, | ||
}); | ||
} | ||
|
||
addOfflineRequest('addItemGlobal', newItem); | ||
|
||
updateItems((prevState: State = {}) => { | ||
const prevItems = Array.isArray(prevState.items) ? prevState.items : []; | ||
|
||
return { | ||
...prevState, | ||
items: [newItem, ...prevItems], | ||
}; | ||
}); | ||
}, | ||
[updateItems], | ||
); | ||
|
||
return { handleImportNewItems }; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { queryTrpc } from '../../trpc'; | ||
|
||
export const useImportPackItem = () => { | ||
const utils = queryTrpc.useContext(); | ||
const mutation = queryTrpc.importItems.useMutation({ | ||
onMutate: async (newItem) => { | ||
if (!newItem) { | ||
throw new Error('Item data is not available.'); | ||
} | ||
|
||
const previousPack = utils.getPackById.getData({ | ||
packId: newItem.packId, | ||
}); | ||
|
||
const newQueryData = { | ||
...previousPack, | ||
items: [ | ||
...(previousPack?.items ?? []), | ||
{ | ||
...newItem, | ||
owners: [], | ||
global: false, | ||
packs: [newItem.id], | ||
id: Date.now().toString(), | ||
category: newItem.type ? { name: newItem.type } : null, | ||
}, | ||
], | ||
}; | ||
|
||
utils.getPackById.setData( | ||
{ packId: newItem.packId }, | ||
newQueryData as any, | ||
); | ||
|
||
return { | ||
previousPack, | ||
}; | ||
}, | ||
onSuccess: () => { | ||
utils.getPackById.invalidate(); | ||
utils.getPacks.invalidate(); | ||
}, | ||
}); | ||
|
||
return { | ||
mutation, | ||
importPackItem: mutation.mutate, | ||
isLoading: mutation.isLoading, | ||
isError: mutation.isError, | ||
error: mutation.error, | ||
}; | ||
}; |
Oops, something went wrong.