mirror of
https://github.com/ForFarmTeam/ForFarm.git
synced 2025-12-19 22:14:08 +01:00
Merge branch 'feature-inventory' of https://github.com/ForFarmTeam/ForFarm into feature-inventory
This commit is contained in:
commit
061d925255
@ -3,6 +3,8 @@ import type {
|
|||||||
InventoryItem,
|
InventoryItem,
|
||||||
InventoryItemStatus,
|
InventoryItemStatus,
|
||||||
InventoryItemCategory,
|
InventoryItemCategory,
|
||||||
|
CreateInventoryItemInput,
|
||||||
|
UpdateInventoryItemInput,
|
||||||
} from "@/types";
|
} from "@/types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,9 +41,11 @@ export async function fetchInventoryCategory(): Promise<
|
|||||||
|
|
||||||
export async function fetchInventoryItems(): Promise<InventoryItem[]> {
|
export async function fetchInventoryItems(): Promise<InventoryItem[]> {
|
||||||
try {
|
try {
|
||||||
const response = await axiosInstance.get<InventoryItem[]>("/api/inventory");
|
const response = await axiosInstance.get<InventoryItem[]>("/inventory");
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// console.error("Error while fetching inventory items! " + error);
|
||||||
|
// throw error;
|
||||||
// Fallback dummy data
|
// Fallback dummy data
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -93,25 +97,42 @@ export async function fetchInventoryItems(): Promise<InventoryItem[]> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Simulates creating a new inventory item.
|
|
||||||
* Uses axios POST and if unavailable, returns a simulated response.
|
|
||||||
*
|
|
||||||
* Note: The function accepts all fields except id, lastUpdated, and status.
|
|
||||||
*/
|
|
||||||
export async function createInventoryItem(
|
export async function createInventoryItem(
|
||||||
item: Omit<InventoryItem, "id" | "lastUpdated" | "status">
|
item: Omit<CreateInventoryItemInput, "id" | "lastUpdated" | "status">
|
||||||
): Promise<InventoryItem> {
|
): Promise<InventoryItem> {
|
||||||
// Simulate network delay
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
||||||
try {
|
try {
|
||||||
const response = await axiosInstance.post<InventoryItem>(
|
const response = await axiosInstance.post<InventoryItem>(
|
||||||
"/api/inventory",
|
"/inventory",
|
||||||
item
|
item
|
||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error while creating Inventory Item!" + error);
|
console.error("Error while creating Inventory Item! " + error);
|
||||||
throw new Error("Failed to create inventory item: " + error);
|
throw new Error("Failed to create inventory item: " + error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deleteInventoryItem(id: string) {
|
||||||
|
try {
|
||||||
|
const response = await axiosInstance.delete("/inventory/" + id);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error while deleting Inventory Item! " + error);
|
||||||
|
throw new Error("Failed to deleting inventory item: " + error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export async function updateInventoryItem(
|
||||||
|
id: string,
|
||||||
|
item: UpdateInventoryItemInput
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const response = await axiosInstance.put<InventoryItem>(
|
||||||
|
"/inventory/" + id,
|
||||||
|
item
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error while updating Inventory Item! " + error);
|
||||||
|
throw new Error("Failed to updating inventory item: " + error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -60,23 +60,13 @@ export function AddInventoryItem({
|
|||||||
const [itemUnit, setItemUnit] = useState("");
|
const [itemUnit, setItemUnit] = useState("");
|
||||||
const [itemStatus, setItemStatus] = useState("");
|
const [itemStatus, setItemStatus] = useState("");
|
||||||
|
|
||||||
// const {
|
|
||||||
// data: inventoryItems = [],
|
|
||||||
// isLoading: isItemLoading,
|
|
||||||
// isError: isItemError,
|
|
||||||
// } = useQuery({
|
|
||||||
// queryKey: ["inventoryItems"],
|
|
||||||
// queryFn: fetchInventoryItems,
|
|
||||||
// staleTime: 60 * 1000,
|
|
||||||
// });
|
|
||||||
|
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationFn: (item: CreateInventoryItemInput) => createInventoryItem(item),
|
mutationFn: (item: CreateInventoryItemInput) => createInventoryItem(item),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
// Invalidate queries to refresh inventory data.
|
// invalidate queries to refresh inventory data.
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
queryClient.invalidateQueries({ queryKey: ["inventoryItems"] });
|
queryClient.invalidateQueries({ queryKey: ["inventoryItems"] });
|
||||||
// Reset form fields and close dialog.
|
// reset form fields and close dialog.
|
||||||
setItemName("");
|
setItemName("");
|
||||||
setItemCategory("");
|
setItemCategory("");
|
||||||
setItemQuantity(0);
|
setItemQuantity(0);
|
||||||
@ -86,9 +76,27 @@ export function AddInventoryItem({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const inputStates = [itemName, itemCategory, itemUnit, itemStatus, date];
|
||||||
|
const isInputValid = inputStates.every((input) => input);
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
// Basic validation (you can extend this as needed)
|
if (!isInputValid) {
|
||||||
if (!itemName || !itemCategory || !itemUnit) return;
|
console.error("All fields are required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newItem: CreateInventoryItemInput = {
|
||||||
|
name: itemName,
|
||||||
|
categoryId:
|
||||||
|
inventoryCategory.find((item) => item.name === itemCategory)?.id || 0,
|
||||||
|
quantity: itemQuantity,
|
||||||
|
unitId: harvestUnits.find((item) => item.name === itemUnit)?.id || 0,
|
||||||
|
statusId:
|
||||||
|
inventoryStatus.find((item) => item.name === itemStatus)?.id || 0,
|
||||||
|
lastUpdated: date ? date.toISOString() : new Date().toISOString(),
|
||||||
|
};
|
||||||
|
console.table(newItem);
|
||||||
|
mutation.mutate(newItem);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -163,7 +171,7 @@ export function AddInventoryItem({
|
|||||||
id="quantity"
|
id="quantity"
|
||||||
type="number"
|
type="number"
|
||||||
className="col-span-3"
|
className="col-span-3"
|
||||||
value={itemQuantity}
|
value={itemQuantity === 0 ? "" : itemQuantity}
|
||||||
onChange={(e) => setItemQuantity(Number(e.target.value))}
|
onChange={(e) => setItemQuantity(Number(e.target.value))}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -8,13 +8,26 @@ import {
|
|||||||
DialogFooter,
|
DialogFooter,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { deleteInventoryItem } from "@/api/inventory";
|
||||||
|
|
||||||
export function DeleteInventoryItem({ id }: { id: string }) {
|
export function DeleteInventoryItem({ id }: { id: string }) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const { mutate: deleteItem, status } = useMutation({
|
||||||
|
mutationFn: deleteInventoryItem,
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["inventoryItems"] });
|
||||||
|
setOpen(false); // Close dialog on success
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.error("Failed to delete item:", error);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
console.log(`Item with ID ${id} deleted.`);
|
deleteItem(id.toString());
|
||||||
setOpen(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -47,8 +60,9 @@ export function DeleteInventoryItem({ id }: { id: string }) {
|
|||||||
<Button
|
<Button
|
||||||
className="bg-red-600 hover:bg-red-800 text-white"
|
className="bg-red-600 hover:bg-red-800 text-white"
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
|
disabled={status === "pending"}
|
||||||
>
|
>
|
||||||
Confirm Delete
|
{status === "pending" ? "Deleting..." : "Delete"}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@ -32,8 +32,8 @@ import {
|
|||||||
InventoryItemCategory,
|
InventoryItemCategory,
|
||||||
HarvestUnits,
|
HarvestUnits,
|
||||||
} from "@/types";
|
} from "@/types";
|
||||||
// import { updateInventoryItem } from "@/api/inventory";
|
import { updateInventoryItem } from "@/api/inventory";
|
||||||
// import type { UpdateInventoryItemInput } from "@/types";
|
import type { UpdateInventoryItemInput } from "@/types";
|
||||||
|
|
||||||
export interface EditInventoryItemProps {
|
export interface EditInventoryItemProps {
|
||||||
id: string;
|
id: string;
|
||||||
@ -76,35 +76,73 @@ export function EditInventoryItem({
|
|||||||
(statusItem) => statusItem.id.toString() === statusId
|
(statusItem) => statusItem.id.toString() === statusId
|
||||||
)?.name
|
)?.name
|
||||||
);
|
);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
// const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
// const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
// mutationFn: (item: UpdateInventoryItemInput) => UpdateInventoryItem(item),
|
mutationFn: (item: UpdateInventoryItemInput) =>
|
||||||
// onSuccess: () => {
|
updateInventoryItem(id, item),
|
||||||
// // Invalidate queries to refresh inventory data.
|
onSuccess: () => {
|
||||||
// queryClient.invalidateQueries({ queryKey: ["inventoryItems"] });
|
// invalidate queries to refresh inventory data.
|
||||||
// // Reset form fields and close dialog.
|
queryClient.invalidateQueries({ queryKey: ["inventoryItems"] });
|
||||||
// setItemName("");
|
// reset form fields and close dialog.
|
||||||
// setItemType("");
|
setItemName("");
|
||||||
// setItemCategory("");
|
setItemCategory("");
|
||||||
// setItemQuantity(0);
|
setItemQuantity(0);
|
||||||
// setItemUnit("");
|
setItemUnit("");
|
||||||
// setDate(undefined);
|
setOpen(false);
|
||||||
// setOpen(false);
|
setItemStatus("");
|
||||||
// },
|
},
|
||||||
// });
|
});
|
||||||
|
|
||||||
|
// send edit request
|
||||||
const handleEdit = () => {
|
const handleEdit = () => {
|
||||||
// // Basic validation (you can extend this as needed)
|
if (!itemName || !itemCategory || !itemUnit) {
|
||||||
// if (!itemName || !itemType || !itemCategory || !itemUnit) return;
|
setError("All fields are required. Please fill in missing details.");
|
||||||
// mutation.mutate({
|
return;
|
||||||
// name: itemName,
|
}
|
||||||
// type: itemType,
|
|
||||||
// category: itemCategory,
|
const category = fetchedInventoryCategory.find(
|
||||||
// quantity: itemQuantity,
|
(c) => c.name === itemCategory
|
||||||
// unit: itemUnit,
|
);
|
||||||
// });
|
const unit = fetchedHarvestUnits.find((u) => u.name === itemUnit);
|
||||||
|
const status = fetchedInventoryStatus.find((s) => s.name === itemStatus);
|
||||||
|
|
||||||
|
if (!category || !unit || !status) {
|
||||||
|
setError(
|
||||||
|
"Invalid category, unit, or status. Please select a valid option."
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// console.table({
|
||||||
|
// name: itemName,
|
||||||
|
// categoryId:
|
||||||
|
// fetchedInventoryCategory.find(
|
||||||
|
// (category) => category.name === itemCategory
|
||||||
|
// )?.id ?? 0,
|
||||||
|
// quantity: itemQuantity,
|
||||||
|
// unitId:
|
||||||
|
// fetchedHarvestUnits.find((unit) => unit.name === itemUnit)?.id ?? 0,
|
||||||
|
// statusId:
|
||||||
|
// fetchedInventoryStatus.find((status) => status.name === itemStatus)
|
||||||
|
// ?.id ?? 0,
|
||||||
|
// lastUpdated: new Date().toISOString(),
|
||||||
|
// });
|
||||||
|
mutation.mutate({
|
||||||
|
name: itemName,
|
||||||
|
categoryId:
|
||||||
|
fetchedInventoryCategory.find(
|
||||||
|
(category) => category.name === itemCategory
|
||||||
|
)?.id ?? 0,
|
||||||
|
quantity: itemQuantity,
|
||||||
|
unitId:
|
||||||
|
fetchedHarvestUnits.find((unit) => unit.name === itemUnit)?.id ?? 0,
|
||||||
|
statusId:
|
||||||
|
fetchedInventoryStatus.find((status) => status.name === itemStatus)
|
||||||
|
?.id ?? 0,
|
||||||
|
lastUpdated: new Date().toISOString(),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -205,6 +243,7 @@ export function EditInventoryItem({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
|
{error && <p className="text-red-500 text-sm">{error}</p>}
|
||||||
<Button type="submit" onClick={handleEdit}>
|
<Button type="submit" onClick={handleEdit}>
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -22,11 +22,13 @@ import {
|
|||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
|
import { TriangleAlertIcon } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
Pagination,
|
Pagination,
|
||||||
PaginationContent,
|
PaginationContent,
|
||||||
PaginationItem,
|
PaginationItem,
|
||||||
} from "@/components/ui/pagination";
|
} from "@/components/ui/pagination";
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
import { Search } from "lucide-react";
|
import { Search } from "lucide-react";
|
||||||
import { FaSort, FaSortUp, FaSortDown } from "react-icons/fa";
|
import { FaSort, FaSortUp, FaSortDown } from "react-icons/fa";
|
||||||
import { fetchHarvestUnits } from "@/api/harvest";
|
import { fetchHarvestUnits } from "@/api/harvest";
|
||||||
@ -92,6 +94,8 @@ export default function InventoryPage() {
|
|||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
// console.table(inventoryItems);
|
// console.table(inventoryItems);
|
||||||
// console.table(inventoryStatus);
|
// console.table(inventoryStatus);
|
||||||
|
// console.table(harvestUnits);
|
||||||
|
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const filteredItems = useMemo(() => {
|
const filteredItems = useMemo(() => {
|
||||||
return inventoryItems
|
return inventoryItems
|
||||||
@ -195,6 +199,22 @@ export default function InventoryPage() {
|
|||||||
const isLoading = loadingStates.some((loading) => loading);
|
const isLoading = loadingStates.some((loading) => loading);
|
||||||
const isError = errorStates.some((error) => error);
|
const isError = errorStates.some((error) => error);
|
||||||
|
|
||||||
|
if (inventoryItems.length === 0) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center h-[50vh]">
|
||||||
|
<Alert variant="destructive" className="w-full max-w-md text-center">
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<TriangleAlertIcon className="h-6 w-6 text-red-500 mb-2" />
|
||||||
|
<AlertTitle>No Inventory Data</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
You currently have no inventory items. Add a new item to get
|
||||||
|
started!
|
||||||
|
</AlertDescription>
|
||||||
|
</div>
|
||||||
|
</Alert>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
if (isLoading)
|
if (isLoading)
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen items-center justify-center">
|
<div className="flex min-h-screen items-center justify-center">
|
||||||
|
|||||||
@ -82,10 +82,16 @@ export type HarvestUnits = {
|
|||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CreateInventoryItemInput = Omit<
|
export type CreateInventoryItemInput = {
|
||||||
InventoryItem,
|
name: string;
|
||||||
"id" | "lastUpdated" | "status"
|
categoryId: number;
|
||||||
>;
|
quantity: number;
|
||||||
|
unitId: number;
|
||||||
|
lastUpdated: string;
|
||||||
|
statusId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UpdateInventoryItemInput = CreateInventoryItemInput & {};
|
||||||
|
|
||||||
export interface Blog {
|
export interface Blog {
|
||||||
id: number;
|
id: number;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user