Skip to content

Commit

Permalink
Merge pull request #1127 from andrew-bierman/feat/import_items
Browse files Browse the repository at this point in the history
Import Items for packs and global items
  • Loading branch information
andrew-bierman authored Jul 26, 2024
2 parents a1b80fc + d4298d5 commit 35bdf2e
Show file tree
Hide file tree
Showing 17 changed files with 582 additions and 22 deletions.
122 changes: 122 additions & 0 deletions packages/app/components/item/ImportForm.tsx
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>
);
};
33 changes: 33 additions & 0 deletions packages/app/components/item/ImportItem.tsx
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>
);
};
29 changes: 29 additions & 0 deletions packages/app/components/item/ImportItemGlobal.tsx
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>
);
};
33 changes: 33 additions & 0 deletions packages/app/components/pack/ImportItemModal.tsx
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>
);
};
41 changes: 28 additions & 13 deletions packages/app/components/pack/PackDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import Layout from 'app/components/layout/Layout';
import { useIsAuthUserPack } from 'app/hooks/packs/useIsAuthUserPack';
import { usePackId } from 'app/hooks/packs/usePackId';
import { useUserPacks } from 'app/hooks/packs/useUserPacks';
import useCustomStyles from 'app/hooks/useCustomStyles';
import useResponsive from 'app/hooks/useResponsive';
import { FlatList, Platform, View } from 'react-native';
import { useFetchSinglePack } from '../../hooks/packs';
Expand All @@ -16,6 +15,7 @@ import ChatContainer from '../chat';
import { DetailsComponent } from '../details';
import { TableContainer } from '../pack_table/Table';
import { AddItemModal } from './AddItemModal';
import { ImportItemModal } from './ImportItemModal';

const SECTION = {
TABLE: 'TABLE',
Expand All @@ -29,15 +29,12 @@ export function PackDetails() {
const canCopy = false;
const [packId] = usePackId();
const link = `${CLIENT_URL}/packs/${packId}`;
const [firstLoad, setFirstLoad] = useState(true);
const user = useAuthUser();
const userId = user?.id;
const [isAddItemModalOpen, setIsAddItemModalOpen] = useState(false);
const [isImportItemModalOpen, setIsImportItemModalOpen] = useState(false);
const [refetch, setRefetch] = useState(false);
const { xxs, xxl, xs } = useResponsive();

const { data: userPacks, isLoading: isUserPacksLoading } =
useUserPacks(userId);
const {
data: currentPack,
isLoading,
Expand Down Expand Up @@ -88,14 +85,32 @@ export function PackDetails() {
);
case SECTION.CTA:
return isAuthUserPack ? (
<AddItemModal
currentPackId={currentPackId || ''}
currentPack={currentPack}
isAddItemModalOpen={isAddItemModalOpen}
setIsAddItemModalOpen={setIsAddItemModalOpen}
// refetch={refetch}
setRefetch={() => setRefetch((prev) => !prev)}
/>
<View
style={{
display: 'flex',
flexDirection: 'row',
width: '100%',
justifyContent: 'center',
gap: 5,
}}
>
<AddItemModal
currentPackId={currentPackId || ''}
currentPack={currentPack}
isAddItemModalOpen={isAddItemModalOpen}
setIsAddItemModalOpen={setIsAddItemModalOpen}
// refetch={refetch}
setRefetch={() => setRefetch((prev) => !prev)}
/>
<ImportItemModal
currentPackId={currentPackId || ''}
currentPack={currentPack}
isImportItemModalOpen={isImportItemModalOpen}
setIsImportItemModalOpen={
setIsImportItemModalOpen
}
/>
</View>
) : null;
case SECTION.SCORECARD:
return (
Expand Down
2 changes: 2 additions & 0 deletions packages/app/components/pack_table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ export const TableContainer = ({
});
const { deletePackItem } = useDeletePackItem();

console.log('data', data);

if (isLoading) return <RSkeleton style={{}} />;
if (error) return <ErrorMessage message={String(error)} />;

Expand Down
41 changes: 41 additions & 0 deletions packages/app/hooks/items/useImportItem.ts
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 };
};
52 changes: 52 additions & 0 deletions packages/app/hooks/packs/useImportPackItem.ts
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,
};
};
Loading

0 comments on commit 35bdf2e

Please sign in to comment.