Merge pull request #28 from Pup-Ion-Dev/add-barcode-field

Add barcode field
This commit is contained in:
Drew Rautenberg
2025-02-06 09:04:18 -06:00
committed by GitHub
12 changed files with 238 additions and 44 deletions
@@ -0,0 +1,108 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using barkmanapi;
#nullable disable
namespace barkmanapi.Migrations
{
[DbContext(typeof(BarkContext))]
[Migration("20250203160519_add-barcodes")]
partial class addbarcodes
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.1")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("barkmanapi.InventoryItems", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("Barcode")
.HasColumnType("integer")
.HasColumnName("barcode");
b.Property<string>("Brand")
.IsRequired()
.HasColumnType("text")
.HasColumnName("brand");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text")
.HasColumnName("name");
b.Property<string>("Notes")
.HasColumnType("text")
.HasColumnName("notes");
b.Property<float?>("RentalPrice")
.HasColumnType("real")
.HasColumnName("rental_price");
b.Property<float?>("ReplacementCost")
.HasColumnType("real")
.HasColumnName("replacement_cost");
b.Property<string>("SerialNumber")
.HasColumnType("text")
.HasColumnName("serial_number");
b.Property<string>("StatusId")
.HasColumnType("text")
.HasColumnName("status_id");
b.HasKey("Id")
.HasName("pk_inventory");
b.HasIndex("StatusId")
.HasDatabaseName("ix_inventory_status_id");
b.ToTable("inventory", (string)null);
});
modelBuilder.Entity("barkmanapi.ItemStatus", b =>
{
b.Property<string>("Id")
.HasColumnType("text")
.HasColumnName("id");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text")
.HasColumnName("name");
b.HasKey("Id")
.HasName("pk_item_status");
b.ToTable("item_status", (string)null);
});
modelBuilder.Entity("barkmanapi.InventoryItems", b =>
{
b.HasOne("barkmanapi.ItemStatus", "Status")
.WithMany()
.HasForeignKey("StatusId")
.HasConstraintName("fk_inventory_item_status_status_id");
b.Navigation("Status");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace barkmanapi.Migrations
{
/// <inheritdoc />
public partial class addbarcodes : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "barcode",
table: "inventory",
type: "integer",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "barcode",
table: "inventory");
}
}
}
@@ -30,6 +30,10 @@ namespace barkmanapi.Migrations
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("Barcode")
.HasColumnType("integer")
.HasColumnName("barcode");
b.Property<string>("Brand") b.Property<string>("Brand")
.IsRequired() .IsRequired()
.HasColumnType("text") .HasColumnType("text")
+4 -2
View File
@@ -48,7 +48,7 @@ var itemStatusGroup = app.MapGroup(prefix: "/itemstatus")
.WithDescription("Endpoints for managing item status"); .WithDescription("Endpoints for managing item status");
inventoryGroup.MapGet("", async (BarkContext db) => inventoryGroup.MapGet("", async (BarkContext db) =>
await db.Inventory.OrderBy(item => item.Id).ToListAsync()); await db.Inventory.OrderBy(item => item.Barcode).ToListAsync());
inventoryGroup.MapGet("/{id}", async (int id, BarkContext db) => inventoryGroup.MapGet("/{id}", async (int id, BarkContext db) =>
{ {
@@ -73,9 +73,10 @@ inventoryGroup.MapPut("/{id}", async (int id, InventoryItems updatedItem, BarkCo
} }
existingItem.Name = updatedItem.Name; existingItem.Name = updatedItem.Name;
existingItem.Barcode = updatedItem.Barcode;
existingItem.Brand = updatedItem.Brand; existingItem.Brand = updatedItem.Brand;
existingItem.SerialNumber = updatedItem.SerialNumber; existingItem.SerialNumber = updatedItem.SerialNumber;
existingItem.Status = updatedItem.Status; existingItem.StatusId = updatedItem.StatusId;
existingItem.RentalPrice = updatedItem.RentalPrice; existingItem.RentalPrice = updatedItem.RentalPrice;
existingItem.ReplacementCost = updatedItem.ReplacementCost; existingItem.ReplacementCost = updatedItem.ReplacementCost;
existingItem.Notes = updatedItem.Notes; existingItem.Notes = updatedItem.Notes;
@@ -88,6 +89,7 @@ inventoryGroup.MapPost("", async (InventoryItems newItemInput, BarkContext db) =
{ {
var newItem = new InventoryItems(); var newItem = new InventoryItems();
newItem.Name = newItemInput.Name; newItem.Name = newItemInput.Name;
newItem.Barcode = newItemInput.Barcode;
newItem.Brand = newItemInput.Brand; newItem.Brand = newItemInput.Brand;
newItem.SerialNumber = newItemInput.SerialNumber; newItem.SerialNumber = newItemInput.SerialNumber;
newItem.RentalPrice = newItemInput.RentalPrice; newItem.RentalPrice = newItemInput.RentalPrice;
+1
View File
@@ -11,6 +11,7 @@ public class BarkContext(DbContextOptions<BarkContext> options) : DbContext(opti
public class InventoryItems public class InventoryItems
{ {
public int Id {get; set;} public int Id {get; set;}
public int Barcode {get; set;}
public string Name { get; set; } public string Name { get; set; }
public string Brand { get; set; } public string Brand { get; set; }
public string? SerialNumber { get; set; } public string? SerialNumber { get; set; }
+3 -3
View File
@@ -43,9 +43,9 @@ function App() {
<Routes> <Routes>
<Route index element={<Home/>}/> <Route index element={<Home/>}/>
<Route path="inventory" element={<InventoryList/>}/> <Route path="inventory" element={<InventoryList/>}/>
<Route path="itemDetail/:itemId" element={<ItemDetail/>}/> <Route path="inventory/itemDetail/:itemId" element={<ItemDetail/>}/>
<Route path="editItem/:itemId" element={<EditItem/>}/> <Route path="inventory/editItem/:itemId" element={<EditItem/>}/>
<Route path="addItem" element={<AddItem/>}/> <Route path="inventory/addItem" element={<AddItem/>}/>
</Routes> </Routes>
</> </>
</QueryClientProvider> </QueryClientProvider>
+30 -9
View File
@@ -2,17 +2,23 @@ import {Button, Group, TextInput, NumberInput, Container, Title, Flex} from '@ma
import {useForm} from '@mantine/form'; import {useForm} from '@mantine/form';
import {useMutation} from "@tanstack/react-query"; import {useMutation} from "@tanstack/react-query";
import {NewItem} from "./types.ts"; import {NewItem} from "./types.ts";
import { useNavigate} from "react-router"; import {useNavigate} from "react-router";
import { IconX, IconCheck } from '@tabler/icons-react'; import {IconX, IconCheck} from '@tabler/icons-react';
import { notifications } from '@mantine/notifications'; import {notifications} from '@mantine/notifications';
import useInventoryList from "./hooks/useInventoryList.tsx";
import {useEffect} from "react";
function AddItem() { function AddItem() {
const navigate = useNavigate(); const navigate = useNavigate();
const inventoryQuery = useInventoryList();
const newItemForm = useForm<NewItem>({ const newItemForm = useForm<NewItem>({
mode: 'uncontrolled', mode: 'uncontrolled',
initialValues: { initialValues: {
barcode: 0,
name: "", name: "",
brand: "", brand: "",
serialNumber: "", serialNumber: "",
@@ -36,8 +42,8 @@ function AddItem() {
if (result.ok) { if (result.ok) {
notifications.show({ notifications.show({
icon: <IconCheck size={20} />, icon: <IconCheck size={20}/>,
color:"teal", color: "teal",
title: "All good!", title: "All good!",
message: "Item Created", message: "Item Created",
position: 'top-center', position: 'top-center',
@@ -48,8 +54,8 @@ function AddItem() {
if (!result.ok) { if (!result.ok) {
notifications.show({ notifications.show({
icon: <IconX size={20} />, icon: <IconX size={20}/>,
color:"red", color: "red",
title: "Bummer!", title: "Bummer!",
message: "Something went wrong", message: "Something went wrong",
position: 'top-center', position: 'top-center',
@@ -57,9 +63,23 @@ function AddItem() {
throw new Error('Failed to create inventory item'); throw new Error('Failed to create inventory item');
} }
} },
}) })
useEffect(() => {
if (inventoryQuery.data) {
const nextBarcode = inventoryQuery.data[inventoryQuery.data.length - 1].barcode + 1;
newItemForm.setValues({barcode: nextBarcode});
}
},[inventoryQuery.data]);
if (inventoryQuery.isPending) return 'Loading...'
if (inventoryQuery.error) return 'An error has occurred: ' + inventoryQuery.error.message
return ( return (
<> <>
@@ -74,7 +94,8 @@ function AddItem() {
wrap="wrap"> wrap="wrap">
<Title order={1}>Add Item</Title> <Title order={1}>Add Item</Title>
<NumberInput withAsterisk size="md" key={newItemForm.key('barcode')} label="Barcode"
placeholder="Barcode" {...newItemForm.getInputProps('barcode')}/>
<TextInput withAsterisk key={newItemForm.key('brand')} size="md" label="Brand" <TextInput withAsterisk key={newItemForm.key('brand')} size="md" label="Brand"
placeholder="Brand" {...newItemForm.getInputProps('brand')}/> placeholder="Brand" {...newItemForm.getInputProps('brand')}/>
<TextInput withAsterisk key={newItemForm.key('name')} size="md" label="Name" <TextInput withAsterisk key={newItemForm.key('name')} size="md" label="Name"
+27 -3
View File
@@ -1,18 +1,22 @@
import {Button, Group, TextInput, Text, Textarea, NumberInput, Container, Title, Flex} from '@mantine/core'; import {Button, Group, TextInput, Textarea, NumberInput, Container, Title, Flex} from '@mantine/core';
import {useForm} from '@mantine/form'; import {useForm} from '@mantine/form';
import {useParams} from "react-router"; import {useNavigate, useParams} from "react-router";
import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query"; import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query";
import {InventoryItem} from "./types.ts"; import {InventoryItem} from "./types.ts";
import {useEffect} from "react"; import {useEffect} from "react";
import {notifications} from "@mantine/notifications";
import {IconCheck, IconX} from "@tabler/icons-react";
type EditableInventoryItem = Omit<InventoryItem, 'id'>; type EditableInventoryItem = Omit<InventoryItem, 'id'>;
function EditItem() { function EditItem() {
const params = useParams(); const params = useParams();
const navigate = useNavigate();
const editItemForm = useForm<EditableInventoryItem>({ const editItemForm = useForm<EditableInventoryItem>({
mode: 'uncontrolled', mode: 'uncontrolled',
initialValues: { initialValues: {
barcode: 0,
name: "", name: "",
brand: "", brand: "",
statusId: "", statusId: "",
@@ -53,7 +57,26 @@ function EditItem() {
} }
}); });
if (result.ok) {
notifications.show({
icon: <IconCheck size={20} />,
color:"teal",
title: "All good!",
message: "Item Updated",
position: 'top-center',
});
navigate("/inventory");
}
if (!result.ok) { if (!result.ok) {
notifications.show({
icon: <IconX size={20} />,
color:"red",
title: "Bummer!",
message: "Something went wrong",
position: 'top-center',
});
throw new Error('Failed to update inventory item'); throw new Error('Failed to update inventory item');
} }
@@ -90,7 +113,8 @@ function EditItem() {
<div>{isFetching ? 'Updating...' : ''}</div> <div>{isFetching ? 'Updating...' : ''}</div>
<Title order={1}>Edit Item</Title> <Title order={1}>Edit Item</Title>
<Text>ID: {data.id}</Text> <NumberInput withAsterisk size="md" key={editItemForm.key('barcode')} label="Barcode"
placeholder="Barcode" {...editItemForm.getInputProps('barcode')}/>
<TextInput withAsterisk key={editItemForm.key('brand')} size="md" label="Brand" <TextInput withAsterisk key={editItemForm.key('brand')} size="md" label="Brand"
placeholder="Brand" {...editItemForm.getInputProps('brand')}/> placeholder="Brand" {...editItemForm.getInputProps('brand')}/>
<TextInput withAsterisk key={editItemForm.key('name')} size="md" label="Name" <TextInput withAsterisk key={editItemForm.key('name')} size="md" label="Name"
@@ -1,27 +1,16 @@
import {useQuery} from "@tanstack/react-query";
import {InventoryItem} from "./types";
import {Flex, Table} from '@mantine/core'; import {Flex, Table} from '@mantine/core';
import BarkButton from "../../common/components/BarkButton.tsx"; import BarkButton from "../../common/components/BarkButton.tsx";
import {Link, NavLink} from "react-router"; import {Link, NavLink} from "react-router";
import useInventoryList from "./hooks/useInventoryList.tsx";
function InventoryList() { function InventoryList() {
const { isPending, error, data, isFetching } = useQuery({ const inventoryQuery = useInventoryList();
queryKey: ['inventory'],
queryFn: async (): Promise<InventoryItem[]> => {
const response = await fetch(
import.meta.env.VITE_API_URL + '/inventory',
)
if (!response.ok) throw new Error('Failed to fetch inventory ' + response.statusText) if (inventoryQuery.isPending) return 'Loading...'
return await response.json() if (inventoryQuery.error) return 'An error has occurred: ' + inventoryQuery.error.message
},
});
if (isPending) return 'Loading...'
if (error) return 'An error has occurred: ' + error.message
return ( return (
@@ -37,16 +26,13 @@ function InventoryList() {
<p> <p>
ARFF ARFF BARK BARK ARFF ARFF BARK BARK
</p> </p>
<NavLink to={'/addItem'}><BarkButton>Add Item</BarkButton></NavLink> <NavLink to={'/inventory/addItem'}><BarkButton>Add Item</BarkButton></NavLink>
</Flex> </Flex>
<div>{isFetching ? 'Updating...' : ''}</div>
<Table striped> <Table striped>
<Table.Thead> <Table.Thead>
<Table.Tr> <Table.Tr>
<Table.Th c="red">ID</Table.Th> <Table.Th c="red">Barcode</Table.Th>
<Table.Th c="red">Brand</Table.Th> <Table.Th c="red">Brand</Table.Th>
<Table.Th c="red">Item</Table.Th> <Table.Th c="red">Item</Table.Th>
<Table.Th c="red">Status</Table.Th> <Table.Th c="red">Status</Table.Th>
@@ -54,14 +40,14 @@ function InventoryList() {
</Table.Tr> </Table.Tr>
</Table.Thead> </Table.Thead>
<Table.Tbody> <Table.Tbody>
{data.map((data) => ( {inventoryQuery.data?.map((data) => (
<Table.Tr key={data.id}> <Table.Tr key={data.id}>
<Table.Td>{data.id}</Table.Td> <Table.Td>{data.barcode}</Table.Td>
<Table.Td>{data.brand}</Table.Td> <Table.Td>{data.brand}</Table.Td>
<Table.Td>{data.name}</Table.Td> <Table.Td>{data.name}</Table.Td>
<Table.Td>{data.statusId}</Table.Td> <Table.Td>{data.statusId}</Table.Td>
<Table.Td> <Table.Td>
<Link to={`/itemDetail/${data.id}`}>Details</Link> <Link to={`/inventory/itemDetail/${data.id}`}>Details</Link>
</Table.Td> </Table.Td>
</Table.Tr> </Table.Tr>
))} ))}
@@ -29,7 +29,7 @@ function ItemDetail() {
<Container m="lg"> <Container m="lg">
<Title order={1}>Item Detail</Title> <Title order={1}>Item Detail</Title>
<div>{isFetching ? 'Updating...' : ''}</div> <div>{isFetching ? 'Updating...' : ''}</div>
<Text>ID: {data.id}</Text> <Text>Barcode: {data.barcode}</Text>
<Text>Brand: {data.brand}</Text> <Text>Brand: {data.brand}</Text>
<Text>Name: {data.name}</Text> <Text>Name: {data.name}</Text>
<Text>Status: {data.status.name}</Text> <Text>Status: {data.status.name}</Text>
@@ -38,7 +38,7 @@ function ItemDetail() {
<Text>Replacement Cost: ${data.replacementCost}</Text> <Text>Replacement Cost: ${data.replacementCost}</Text>
<Text>Notes: {data.notes}</Text> <Text>Notes: {data.notes}</Text>
<Link to={`/editItem/${data.id}`}>Edit</Link> <Link to={`/inventory/editItem/${data.id}`}>Edit</Link>
</Container> </Container>
</> </>
) )
@@ -0,0 +1,17 @@
import {useQuery} from "@tanstack/react-query";
import {InventoryItem} from "../types.js";
const useInventoryList = () => useQuery({
queryKey: ['inventory'],
queryFn: async (): Promise<InventoryItem[]> => {
const response = await fetch(
import.meta.env.VITE_API_URL + '/inventory',
)
if (!response.ok) throw new Error('Failed to fetch inventory ' + response.statusText)
return await response.json()
},
});
export default useInventoryList;
@@ -1,5 +1,6 @@
export interface InventoryItem { export interface InventoryItem {
id: number, id: number,
barcode: number,
brand: string, brand: string,
name: string, name: string,
status: {id: string; name: string}, status: {id: string; name: string},
@@ -11,6 +12,7 @@ export interface InventoryItem {
} }
export interface NewItem { export interface NewItem {
barcode: number,
brand: string, brand: string,
name: string, name: string,
serialNumber: string, serialNumber: string,