diff --git a/barkmanAPI/Program.cs b/barkmanAPI/Program.cs index 06b051d..9a9de8e 100644 --- a/barkmanAPI/Program.cs +++ b/barkmanAPI/Program.cs @@ -47,6 +47,10 @@ var itemStatusGroup = app.MapGroup(prefix: "/itemstatus") .WithTags("Item Status") .WithDescription("Endpoints for managing item status"); +var customerGroup = app.MapGroup(prefix: "/customers") + .WithTags("Customers") + .WithDescription("Endpoints for managing customers"); + inventoryGroup.MapGet("", async (BarkContext db) => await db.Inventory.OrderBy(item => item.Barcode).ToListAsync()); @@ -163,6 +167,71 @@ itemStatusGroup.MapDelete("/{id}", async (string id, BarkContext db) => return Results.Ok(new { Message = "Item status deleted successfully" }); }); +customerGroup.MapGet("", async (BarkContext db) => + await db.Customers.OrderBy(customer => customer.Id).ToListAsync()); + +customerGroup.MapGet("/{id}", async (int id, BarkContext db) => +{ + var customer = await db.Customers + .FirstOrDefaultAsync(i => i.Id == id); + + if (customer == null) + { + return Results.NotFound(new { Message = "Customer not found" }); + } + + return Results.Ok(customer); +}); + +customerGroup.MapPut("/{id}", async (int id, Customers updatedCustomer, BarkContext db) => +{ + var existingCustomer = await db.Customers.FindAsync(id); + if (existingCustomer == null) + { + return Results.NotFound(new { Message = "Customer not found" }); + } + + existingCustomer.Name = updatedCustomer.Name; + existingCustomer.Company = updatedCustomer.Company; + existingCustomer.Email = updatedCustomer.Email; + existingCustomer.PhoneNumber = updatedCustomer.PhoneNumber; + existingCustomer.Address = updatedCustomer.Address; + existingCustomer.BillingTerms = updatedCustomer.BillingTerms; + existingCustomer.Notes = updatedCustomer.Notes; + + await db.SaveChangesAsync(); + return Results.Ok(existingCustomer); +}); + +customerGroup.MapPost("", async (Customers newCustomerInput, BarkContext db) => +{ + var newCustomer = new Customers() + { + Name = newCustomerInput.Name, + Email = newCustomerInput.Email, + Company = newCustomerInput.Company, + PhoneNumber = newCustomerInput.PhoneNumber, + Address = newCustomerInput.Address, + }; + + db.Customers.Add(newCustomer); + await db.SaveChangesAsync(); + return Results.Created($"/customers/{newCustomer.Id}", newCustomer); +}); + +customerGroup.MapDelete("/{id}", async (int id, BarkContext db) => +{ + var customer = await db.Customers.FindAsync(id); + if (customer == null) + { + return Results.NotFound(new { Message = "Customer not found" }); + } + + db.Customers.Remove(customer); + await db.SaveChangesAsync(); + return Results.Ok(new { Message = "Customer deleted successfully" }); +}); + using (var serviceScope = app.Services.CreateScope()) { var dbContext = serviceScope.ServiceProvider.GetRequiredService(); diff --git a/barkmanui/src/App.tsx b/barkmanui/src/App.tsx index ac886f0..16fcc53 100644 --- a/barkmanui/src/App.tsx +++ b/barkmanui/src/App.tsx @@ -10,6 +10,10 @@ import {BarkHeader} from "./common/components/BarkHeader.tsx"; import AddItem from "./features/inventory/AddItem.tsx"; import { Notifications } from '@mantine/notifications'; import '@mantine/notifications/styles.css'; +import CustomerList from "@/features/customers/CustomerList.tsx"; +import AddCustomer from "@/features/customers/AddCustomer.tsx"; +import CustomerDetail from "@/features/customers/CustomerDetail.tsx"; +import EditCustomer from "@/features/customers/EditCustomer.tsx"; // Create a client const queryClient = new QueryClient() @@ -46,6 +50,11 @@ function App() { }/> }/> }/> + }/> + }/> + }/> + }/> + diff --git a/barkmanui/src/common/components/BarkHeader.tsx b/barkmanui/src/common/components/BarkHeader.tsx index 7fef196..8ab8ea8 100644 --- a/barkmanui/src/common/components/BarkHeader.tsx +++ b/barkmanui/src/common/components/BarkHeader.tsx @@ -7,6 +7,7 @@ import BarkLogo from '@/assets/barklogo.png'; const links = [ { link: '/', label: 'Home' }, + { link: '/customers', label: 'Customers' }, { link: '/inventory', label: 'Inventory' }, ]; diff --git a/barkmanui/src/features/customers/AddCustomer.tsx b/barkmanui/src/features/customers/AddCustomer.tsx new file mode 100644 index 0000000..1693af2 --- /dev/null +++ b/barkmanui/src/features/customers/AddCustomer.tsx @@ -0,0 +1,109 @@ +import {Button, Group, TextInput, Container, Title, Flex} from '@mantine/core'; +import {useForm} from '@mantine/form'; +import {useMutation} from "@tanstack/react-query"; +import {NewCustomer} from "./types.ts"; +import {useNavigate} from "react-router"; +import {IconX, IconCheck} from '@tabler/icons-react'; +import {notifications} from '@mantine/notifications'; +import useCustomerList from "./hooks/useCustomerList.tsx"; + + +function AddCustomer() { + + const navigate = useNavigate(); + + const customerQuery = useCustomerList(); + + const newCustomerForm = useForm({ + mode: 'uncontrolled', + initialValues: { + name: "", + company: "", + email: "", + phone: "", + address: "", + }, + + validate: {}, + }); + + const updateCustomer = useMutation({ + mutationFn: async (values: NewCustomer) => { + + const result = await fetch(import.meta.env.VITE_API_URL + '/customers', { + method: 'POST', + body: JSON.stringify(values), + headers: { + 'Content-Type': 'application/json' + } + }); + + if (result.ok) { + notifications.show({ + icon: , + color: "teal", + title: "All good!", + message: "Customer Created", + position: 'top-center', + }); + navigate("/customers"); + + } + + if (!result.ok) { + notifications.show({ + icon: , + color: "red", + title: "Bummer!", + message: "Something went wrong", + position: 'top-center', + }); + throw new Error('Failed to create customer'); + } + + }, + + + }) + + if (customerQuery.isPending) return 'Loading...' + if (customerQuery.error) return 'An error has occurred: ' + customerQuery.error.message + + + + + return ( + <> +
await + updateCustomer.mutateAsync(values))}> + + + + Add Item + + + + + + + + + + + +
+ + ); +} + +export default AddCustomer; \ No newline at end of file diff --git a/barkmanui/src/features/customers/CustomerDetail.tsx b/barkmanui/src/features/customers/CustomerDetail.tsx new file mode 100644 index 0000000..5d1d297 --- /dev/null +++ b/barkmanui/src/features/customers/CustomerDetail.tsx @@ -0,0 +1,45 @@ +import {Container, Text, Title} from "@mantine/core"; +import {Link, useParams} from "react-router"; +import {Customer} from "./types.ts"; +import {useQuery} from "@tanstack/react-query"; + + +function CustomerDetail() { + const params = useParams(); + + const {isPending, error, data, isFetching} = useQuery({ + queryKey: ['customers', params.customerId], + queryFn: async (): Promise => { + const response = await fetch( + import.meta.env.VITE_API_URL + '/customers/' + params.customerId, + ) + + if (!response.ok) throw new Error('Failed to fetch customer ' + response.statusText) + + return await response.json() + }, + }); + + if (isPending) return 'Loading...' + + if (error) return 'An error has occurred: ' + error.message + + return ( + <> + + Customer Detail +
{isFetching ? 'Updating...' : ''}
+ Name: {data.name} + Company: {data.company} + Email: {data.email} + Phone Number: {data.phone} + Address: ${data.address} + Notes: {data.notes} + + Edit +
+ + ) +} + +export default CustomerDetail \ No newline at end of file diff --git a/barkmanui/src/features/customers/CustomerList.tsx b/barkmanui/src/features/customers/CustomerList.tsx new file mode 100644 index 0000000..24a7f88 --- /dev/null +++ b/barkmanui/src/features/customers/CustomerList.tsx @@ -0,0 +1,54 @@ +import {Flex, Table} from '@mantine/core'; +import BarkButton from "../../common/components/BarkButton.tsx"; +import {Link, NavLink} from "react-router"; +import useCustomerList from "./hooks/useCustomerList.tsx"; + + +function CustomerList() { + + const customerQuery = useCustomerList(); + + if (customerQuery.isPending) return 'Loading...' + + if (customerQuery.error) return 'An error has occurred: ' + customerQuery.error.message + + + return ( + <> + +

+ ARFF ARFF BARK BARK +

+ Add Customer +
+ + + + + Name + Company + + + + {customerQuery.data?.map((data) => ( + + {data.name} + {data.company} + + Details + + + ))} + +
+ + ) +} +export default CustomerList; \ No newline at end of file diff --git a/barkmanui/src/features/customers/EditCustomer.tsx b/barkmanui/src/features/customers/EditCustomer.tsx new file mode 100644 index 0000000..f1cb229 --- /dev/null +++ b/barkmanui/src/features/customers/EditCustomer.tsx @@ -0,0 +1,134 @@ +import {Button, Group, TextInput, Container, Title, Flex} from '@mantine/core'; +import {useForm} from '@mantine/form'; +import {useNavigate, useParams} from "react-router"; +import {useMutation, useQuery, useQueryClient} from "@tanstack/react-query"; +import {Customer} from "./types.ts"; +import {useEffect} from "react"; +import {notifications} from "@mantine/notifications"; +import {IconCheck, IconX} from "@tabler/icons-react"; + +type EditableCustomer= Omit; + +function EditCustomer() { + const params = useParams(); + const navigate = useNavigate(); + + const editCustomerForm = useForm({ + mode: 'uncontrolled', + initialValues: { + name: "", + company: "", + email: "", + phone: "", + address: "", + billingTerms: "", + notes: "", + }, + + validate: {}, + }); + + const {isPending, error, data, isFetching} = useQuery({ + queryKey: ['customer', params.customerId], + queryFn: async (): Promise => { + const response = await fetch( + import.meta.env.VITE_API_URL + '/customers/' + params.customerId, + ) + + if (!response.ok) throw new Error('Failed to fetch customer ' + response.statusText) + + return await response.json() + }, + }); + + const queryClient = useQueryClient(); + + const updateCustomer = useMutation({ + mutationFn: async (values: EditableCustomer) => { + + // await + const result = await fetch(import.meta.env.VITE_API_URL + '/customers/' + params.customerId, { + method: 'PUT', + body: JSON.stringify(values), + headers: { + 'Content-Type': 'application/json' + } + }); + + if (result.ok) { + notifications.show({ + icon: , + color:"teal", + title: "All good!", + message: "Customer Updated", + position: 'top-center', + }); + navigate("/customers"); + + } + + if (!result.ok) { + notifications.show({ + icon: , + color:"red", + title: "Bummer!", + message: "Something went wrong", + position: 'top-center', + }); + throw new Error('Failed to update customer'); + } + + // invalidate the queries so they pull updated information + // this is a prefix, so it covers both the query that pulls a list, and also `['customers', customerId]` in this file + await queryClient.invalidateQueries({ + queryKey: ['customers'] + }); + } + }) + + useEffect(() => { + if (data) { + // Even if query.data changes, form will be initialized only once + editCustomerForm.initialize(data); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [data]); + + if (isPending) return 'Loading...' + + if (error) return 'An error has occurred: ' + error.message + + return ( +
await + updateCustomer.mutateAsync(values))}> + + + +
{isFetching ? 'Updating...' : ''}
+ Edit Customer + + + + + + + + + +
+
+
+ ); +} + +export default EditCustomer; \ No newline at end of file diff --git a/barkmanui/src/features/customers/hooks/useCustomerList.tsx b/barkmanui/src/features/customers/hooks/useCustomerList.tsx new file mode 100644 index 0000000..293a3a7 --- /dev/null +++ b/barkmanui/src/features/customers/hooks/useCustomerList.tsx @@ -0,0 +1,17 @@ +import {useQuery} from "@tanstack/react-query"; +import {Customer} from "../types.ts"; + + const useCustomerList = () => useQuery({ + queryKey: ['customer'], + queryFn: async (): Promise => { + const response = await fetch( + import.meta.env.VITE_API_URL + '/customers', + ) + + if (!response.ok) throw new Error('Failed to fetch customers ' + response.statusText) + + return await response.json() + }, + }); + +export default useCustomerList; \ No newline at end of file diff --git a/barkmanui/src/features/customers/types.ts b/barkmanui/src/features/customers/types.ts new file mode 100644 index 0000000..6451203 --- /dev/null +++ b/barkmanui/src/features/customers/types.ts @@ -0,0 +1,17 @@ +export interface Customer { + id: number, + name: string, + company: string, + email: string, + phone: string, + address: string, + billingTerms: string, + notes: string, +} +export interface NewCustomer { + name: string, + company: string, + email: string, + phone: string, + address: string, +} \ No newline at end of file