uplodowanie wszystkich zdjęć które są wybrane przy dodawaniu produktu

This commit is contained in:
2025-05-05 15:01:25 +02:00
parent 1a8fe7bb1d
commit 845a2e9593
4 changed files with 1483 additions and 192 deletions

View File

@@ -2,6 +2,7 @@ import axios from "axios";
import FormData from 'form-data' import FormData from 'form-data'
const API_URL = "https://testowe.zikor.pl/api/v1"; const API_URL = "https://testowe.zikor.pl/api/v1";
// const API_URL = "http://172.20.10.2:8080/api/v1"; // const API_URL = "http://172.20.10.2:8080/api/v1";
export async function listNotices() { export async function listNotices() {
@@ -35,7 +36,10 @@ export async function createNotice(notice) {
// console.log("Image url: ", notice.image) // console.log("Image url: ", notice.image)
if (response.data.noticeId !== null) { if (response.data.noticeId !== null) {
uploadImage(response.data.noticeId, notice.image); notice.image.forEach(imageUri => {
uploadImage(response.data.noticeId, imageUri);
});
// uploadImage(response.data.noticeId, notice.image);
} }
} catch (error) { } catch (error) {
@@ -63,18 +67,15 @@ export async function getImageByNoticeId(noticeId) {
export const uploadImage = async (noticeId, imageUri) => { export const uploadImage = async (noticeId, imageUri) => {
console.log("Started upload image"); console.log("Started upload image");
console.log(imageUri);
// Utwórz obiekt FormData
const formData = new FormData(); const formData = new FormData();
// Zdobądź nazwę pliku z URI
const filename = imageUri.split('/').pop(); const filename = imageUri.split('/').pop();
// Określ typ MIME (możesz dostosować lub wykrywać dynamicznie)
const match = /\.(\w+)$/.exec(filename); const match = /\.(\w+)$/.exec(filename);
const type = match ? `image/${match[1]}` : 'image/jpeg'; const type = match ? `image/${match[1]}` : 'image/jpeg';
// Dodaj plik do FormData w formacie akceptowanym przez serwer
formData.append('file', { formData.append('file', {
uri: imageUri, uri: imageUri,
name: filename, name: filename,

View File

@@ -1,207 +1,220 @@
import { useState, useEffect } from "react"; import {useState, useEffect} from "react";
import { Image, StyleSheet } from "react-native"; import {Image, StyleSheet} from "react-native";
import { Button, ButtonText } from "@/components/ui/button"; import {Button, ButtonText} from "@/components/ui/button";
import { FormControl } from "@/components/ui/form-control"; import {FormControl} from "@/components/ui/form-control";
import { Input, InputField } from "@/components/ui/input"; import {Input, InputField} from "@/components/ui/input";
import { Text } from "@/components/ui/text"; import {Text} from "@/components/ui/text";
import { VStack } from "@/components/ui/vstack"; import {VStack} from "@/components/ui/vstack";
import { Textarea, TextareaInput } from "@/components/ui/textarea"; import {Textarea, TextareaInput} from "@/components/ui/textarea";
import {ScrollView} from '@gluestack-ui/themed';
import * as ImagePicker from 'expo-image-picker'; import * as ImagePicker from 'expo-image-picker';
import { import {
Select, Select,
SelectTrigger, SelectTrigger,
SelectInput, SelectInput,
SelectIcon, SelectIcon,
SelectPortal, SelectPortal,
SelectBackdrop, SelectBackdrop,
SelectContent, SelectContent,
SelectItem, SelectItem,
SelectScrollView, SelectScrollView,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { ChevronDownIcon } from "@/components/ui/icon"; import {ChevronDownIcon} from "@/components/ui/icon";
import { useMutation } from "@tanstack/react-query"; import {useMutation} from "@tanstack/react-query";
import {createNotice} from "@/api/notices"; import {createNotice} from "@/api/notices";
import {listCategories} from "@/api/categories"; import {listCategories} from "@/api/categories";
export default function CreateNotice() { export default function CreateNotice() {
const [title, setTitle] = useState(""); const [title, setTitle] = useState("");
const [description, setDescription] = useState(""); const [description, setDescription] = useState("");
const [price, setPrice] = useState(""); const [price, setPrice] = useState("");
const [category, setCategory] = useState(""); const [category, setCategory] = useState("");
const [image, setImage] = useState(null); const [image, setImage] = useState([]);
const [selectItems, setSelectItems] = useState([]); const [selectItems, setSelectItems] = useState([]);
useEffect(() => { useEffect(() => {
let isMounted = true; let isMounted = true;
const fetchSelectItems = async () => { const fetchSelectItems = async () => {
try { try {
let data = await listCategories(); let data = await listCategories();
if (isMounted && Array.isArray(data)) { if (isMounted && Array.isArray(data)) {
setSelectItems(data); setSelectItems(data);
}
} catch (error) {
console.error('Error fetching select items:', error);
}
};
fetchSelectItems();
return () => {
isMounted = false;
};
}, []);
const [error, setError] = useState({
title: false,
description: false,
price: false,
category: false,
});
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
image: {
width: 100,
height: 100,
},
});
const noticeMutation = useMutation({
mutationFn: () =>
createNotice({
title: title,
clientId: 1,
description: description,
price: parseFloat(price),
category: category,
status: "ACTIVE",
image: image,
}),
onSuccess: () => {
console.log("Notice created successfully");
},
onError: (error) => {
console.error("Error creating notice. Erroe message: ", error.message);
},
});
const addNotice = () => {
setError({
title: !title,
description: !description,
price: !price,
category: !category,
});
if (!title || !description || !price || !category) {
console.log("Error in form");
return;
} }
} catch (error) { noticeMutation.mutate();
console.error('Error fetching select items:', error);
}
}; };
fetchSelectItems(); const pickImage = async () => {
// No permissions request is necessary for launching the image library
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ['images'],
selectionLimit: 8,
allowsEditing: false,
allowsMultipleSelection: true,
aspect: [4, 3],
quality: 0.5,
});
return () => { if (!result.canceled) {
isMounted = false; // await uploadImage(1, result.assets[0].uri);
setImage(result.assets.map(asset => asset.uri));
}
}; };
}, []);
const [error, setError] = useState({ return (
title: false, <ScrollView h="$80" w="$80">
description: false, <FormControl className="p-4 border rounded-lg border-outline-300">
price: false, <VStack space="xl">
category: false, <VStack space="md">
}); <Text className="text-typography-500">Zdjęcia</Text>
<Button onPress={pickImage}>
<ButtonText>
Wybierz zdjęcia
</ButtonText>
</Button>
<Text size="sm"
bold="true"
>
Pierwsze zdjęcie będzie zdjęciem głównym</Text>
{image && image.length > 0 && (
<VStack space="xs" className="flex-row flex-wrap">
{image.map((img, index) => (
<Image key={index} source={{uri: img}} style={styles.image} className="m-1"/>
))}
</VStack>
)}
</VStack>
const styles = StyleSheet.create({ <VStack space="xs">
container: { <Text className="text-typography-500">Tytuł</Text>
flex: 1, <Input className="min-w-[250px]" isInvalid={error.title}>
alignItems: 'center', <InputField
justifyContent: 'center', type="text"
}, value={title}
image: { onChangeText={(value) => setTitle(value)}
width: 100, />
height: 100, </Input>
}, </VStack>
});
const noticeMutation = useMutation({ <VStack space="xs">
mutationFn: () => <Text className="text-typography-500">Opis</Text>
createNotice({ <Textarea
title: title, size="md"
clientId: 1, className="min-w-[250px] "
description: description, isInvalid={error.description}
price: parseFloat(price), >
category: category, <TextareaInput
status: "ACTIVE", placeholder="Opisz produkt"
image: image, value={description}
}), onChangeText={(value) => setDescription(value)}
onSuccess: () => { />
console.log("Notice created successfully"); </Textarea>
}, </VStack>
onError: (error) => {
console.error("Error creating notice. Erroe message: ", error.message);
},
});
const addNotice = () => { <VStack space="xs">
setError({ <Text className="text-typography-500">Cena</Text>
title: !title, <Input className="min-w-[250px]" isInvalid={error.price}>
description: !description, <InputField
price: !price, type="text"
category: !category, value={price}
}); onChangeText={(value) => setPrice(value)}
/>
if (!title || !description || !price || !category) { </Input>
console.log("Error in form"); </VStack>
return; <VStack space="xs">
} <Text className="text-typography-500">Kategoria</Text>
noticeMutation.mutate(); <Select
}; onValueChange={(value) => setCategory(value)}
isInvalid={error.category}
const pickImage = async () => { >
// No permissions request is necessary for launching the image library <SelectTrigger variant="outline" size="md">
let result = await ImagePicker.launchImageLibraryAsync({ <SelectInput placeholder="Wybierz kategorię"/>
mediaTypes: ['images'], <SelectIcon className="mr-3" as={ChevronDownIcon}/>
selectionLimit: 8, </SelectTrigger>
allowsEditing: false, <SelectPortal>
allowsMultipleSelection: true, <SelectBackdrop/>
aspect: [4, 3], <SelectContent style={{maxHeight: 400}}>
quality: 0.5, <SelectScrollView>
}); {selectItems.map((item) => (
<SelectItem key={item.value} label={item.label} value={item.value}/>
if (!result.canceled) { ))}
// await uploadImage(1, result.assets[0].uri); </SelectScrollView>
setImage(result.assets[0].uri); </SelectContent>
} </SelectPortal>
}; </Select>
</VStack>
return ( <Button
<FormControl className="p-4 border rounded-lg border-outline-300"> className="mt-5 w-full"
<VStack space="xl"> onPress={() => addNotice()}
<VStack space="xs"> disabled={noticeMutation.isLoading}
<Text className="text-typography-500">Zdjęcie</Text> >
<Button onPress={pickImage}> <ButtonText className="text-typography-0">Dodaj</ButtonText>
<ButtonText> </Button>
Upload image </VStack>
</ButtonText> </FormControl>
</Button> </ScrollView>
{image && <Image source={{ uri: image }} style={styles.image}/>} );
</VStack>
<VStack space="xs">
<Text className="text-typography-500">Tytuł</Text>
<Input className="min-w-[250px]" isInvalid={error.title}>
<InputField
type="text"
value={title}
onChangeText={(value) => setTitle(value)}
/>
</Input>
</VStack>
<VStack space="xs">
<Text className="text-typography-500">Opis</Text>
<Textarea
size="md"
className="min-w-[250px] "
isInvalid={error.description}
>
<TextareaInput
placeholder="Opisz produkt"
value={description}
onChangeText={(value) => setDescription(value)}
/>
</Textarea>
</VStack>
<VStack space="xs">
<Text className="text-typography-500">Cena</Text>
<Input className="min-w-[250px]" isInvalid={error.price}>
<InputField
type="text"
value={price}
onChangeText={(value) => setPrice(value)}
/>
</Input>
</VStack>
<VStack space="xs">
<Text className="text-typography-500">Kategoria</Text>
<Select
onValueChange={(value) => setCategory(value)}
isInvalid={error.category}
>
<SelectTrigger variant="outline" size="md">
<SelectInput placeholder="Wybierz kategorię" />
<SelectIcon className="mr-3" as={ChevronDownIcon} />
</SelectTrigger>
<SelectPortal>
<SelectBackdrop />
<SelectContent style={{ maxHeight: 400 }}>
<SelectScrollView>
{selectItems.map((item) => (
<SelectItem key={item.value} label={item.label} value={item.value} />
))}
</SelectScrollView>
</SelectContent>
</SelectPortal>
</Select>
</VStack>
<Button
className="ml-auto"
onPress={() => addNotice()}
disabled={noticeMutation.isLoading}
>
<ButtonText className="text-typography-0">Save</ButtonText>
</Button>
</VStack>
</FormControl>
);
} }

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,7 @@
"dependencies": { "dependencies": {
"@expo/html-elements": "^0.4.2", "@expo/html-elements": "^0.4.2",
"@expo/vector-icons": "^14.1.0", "@expo/vector-icons": "^14.1.0",
"@gluestack-style/react": "^1.0.57",
"@gluestack-ui/actionsheet": "^0.2.53", "@gluestack-ui/actionsheet": "^0.2.53",
"@gluestack-ui/button": "^1.0.14", "@gluestack-ui/button": "^1.0.14",
"@gluestack-ui/form-control": "^0.1.19", "@gluestack-ui/form-control": "^0.1.19",
@@ -21,6 +22,7 @@
"@gluestack-ui/overlay": "^0.1.22", "@gluestack-ui/overlay": "^0.1.22",
"@gluestack-ui/select": "^0.1.31", "@gluestack-ui/select": "^0.1.31",
"@gluestack-ui/textarea": "^0.1.25", "@gluestack-ui/textarea": "^0.1.25",
"@gluestack-ui/themed": "^1.1.73",
"@gluestack-ui/toast": "^1.0.9", "@gluestack-ui/toast": "^1.0.9",
"@legendapp/motion": "^2.4.0", "@legendapp/motion": "^2.4.0",
"@tanstack/react-query": "^5.74.4", "@tanstack/react-query": "^5.74.4",