Complete basic request dispatch flow
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2024-04-07 19:40:38 +02:00
parent 00cf0d9905
commit 9be7ae81ae
11 changed files with 264 additions and 163 deletions

View File

@ -44,6 +44,7 @@ export interface Maker extends Node {
stock: Product[];
createdAt: string;
updatedAt: string;
admins: User[];
};
export interface Retailer extends Node {
@ -53,16 +54,17 @@ export interface Retailer extends Node {
stock: Product[];
createdAt: string;
updatedAt: string;
admins: User[];
};
const DISPATCH_STATUS = ['requested', 'accepted', 'archived'] as const;
export type DispatchStatus = typeof DISPATCH_STATUS[number];
export interface Dispatch {
id: string;
dispatchesCode?: string; //Human readable id
createdAt: string;
updatedAt: string;
id?: string;
code?: string; //Human readable id
createdAt?: string;
updatedAt?: string;
maker: Maker;
retailer: Retailer;
@ -73,8 +75,19 @@ export interface Dispatch {
timeSensitive: boolean;
status: DispatchStatus;
departureDate: string;
arrivalDate: string;
weightAllowance: number;
departureDate?: string;
arrivalDate?: string;
}
export interface CreateDispatch {
code?: string; //Human readable id
maker: Maker | string;
retailer: Retailer | string;
products: Product[] | string[] ;
courier?: User;
timeSensitive: boolean;
status: DispatchStatus;
}

View File

@ -1,6 +1,8 @@
import { useQuery, useMutation, useQueryClient, queryOptions, QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import type { ReactNode } from "react";
import { KiosMap } from "@/components/KiosMap"
import { KiosMap } from "@/components/KiosMap";
export const queryClient = new QueryClient({
defaultOptions: {
@ -18,6 +20,7 @@ export const App: React.FC<AppProps> = (props) => {
return (
<div className="app">
<QueryClientProvider client={queryClient}>
<ReactQueryDevtools initialIsOpen={false} />
{props.children}
<KiosMap/>
</QueryClientProvider>

View File

@ -3,13 +3,15 @@ import { MapContainer, TileLayer, Marker, CircleMarker, Popup, Polyline, LayerGr
import 'leaflet/dist/leaflet.css';
import L, { LatLngBounds } from 'leaflet';
import Contacts from './Contacts';
import type { User, Node, Retailer, Maker, Product, Dispatch, DispatchStatus } from '../astroTypes';
import type { User, Node, Retailer, Maker, Product, Dispatch, DispatchStatus, CreateDispatch } from '../astroTypes';
import { useQuery, useMutation, useQueryClient, queryOptions } from "@tanstack/react-query";
import { useGetMakers, useGetDispatches, useGetRetailers, useGetUser, useGetMyself } from "../utils/hooks"
import { useGetMakers, useGetDispatches, useGetRetailers, useGetUser, useGetMyself, API_URL, useGetMyRetailers, useCreateDispatch } from "../utils/hooks"
import { Button, buttonVariants } from './ui/Button';
import { humanId, poolSize, minLength, maxLength } from 'human-id'
import {
Dialog,
DialogContent,
@ -21,7 +23,7 @@ import {
import { LoginForm } from './LoginForm';
import { hasAuthCookie } from '@/utils/authUtils';
//Payload longitude and latitude are mislabeled in payload (lol)
//Payload longitude and latitude are mislabeled/switched in payload
const locationSwitcharoo = (location: number[]) => {
if (location.length === 2) {
const correctedLocation = [location[1], location[0]]
@ -59,13 +61,16 @@ interface NodeSelection {
export const KiosMap = () => {
const [authToken, setAuthToken] = useState('')
const [authToken, setAuthToken] = useState<string>('')
const { data: makers, isLoading: isLoadingMakers } = useGetMakers();
const { data: retailers, isLoading: isLoadingRetailers } = useGetRetailers();
const { data: dispatches, isLoading: isLoadingDispatches } = useGetDispatches();
const { data: myself, isLoading: isLoadingMyself } = useGetMyself(authToken);
const { data: myRetailers, isLoading: isLoadingMyRetailers } = useGetMyRetailers(myself);
const createDispatchMutation = useCreateDispatch();
const [selectedNode, setSelectedNode] = useState<NodeSelection>({ id: "", type: "none" })
@ -88,7 +93,6 @@ export const KiosMap = () => {
const handleSelectNode = (nodeId: string, typeParam: "maker" | "retailer" | "dispatch" | "none") => {
setSelectedNode({ id: nodeId, type: typeParam })
console.log("set id:", nodeId)
}
//params: dispatch: Dispatch, courier: User
@ -96,12 +100,24 @@ export const KiosMap = () => {
}
const handleOpenCatalogue = () => {
}
//params
const handleRequestProduct = () => {
const handleRequestDispatch = (products: Product[], retailer: Retailer, maker: Maker | undefined) => {
if (maker === undefined) {
console.error("Request dispatch error: Marker undefined")
return
}
const dispatch: CreateDispatch = {
code: humanId({
separator: '-',
capitalize: false,
}),
products: products.map((product) => {return product.id}),
maker: maker.id,
retailer: retailer.id,
timeSensitive: false,
status: "requested",
}
console.log(dispatch)
createDispatchMutation.mutate(dispatch)
}
const blackDotIcon = L.divIcon({
@ -131,26 +147,22 @@ export const KiosMap = () => {
width={120}
src="/kios-logo.png"
alt="" />
{(myself && myself.name)
?
<p>Logged in as: {myself.name}</p>
: <p>Logged in</p>
}
{
(!hasAuthCookie() && !authToken) &&
(!hasAuthCookie() && !authToken) ?
<DialogTrigger
className={`px-14 w-6 ${buttonVariants({ variant: "kios" })}`}
className={`px-12 ${buttonVariants({ variant: "kios" })}`}
>
Login
</DialogTrigger>
: myself && <p>Logged in as: {myself?.name}</p>
}
</div>
<DialogContent>
<DialogHeader>
<DialogTitle className="text-4xl text-center">Login</DialogTitle>
<LoginForm setAuthToken={setAuthToken} authToken={authToken}/>
<LoginForm setAuthToken={setAuthToken} authToken={authToken} />
</DialogHeader>
</DialogContent>
</Dialog>
@ -173,7 +185,7 @@ export const KiosMap = () => {
phoneNumber={selectedMaker.phoneNumber}
role={'maker'}
/>
{(selectedMaker.stock !== undefined && selectedMaker.stock.length > 0) &&
{(selectedMaker !== undefined && selectedMaker.stock !== undefined && selectedMaker.stock.length > 0) &&
<Dialog>
<DialogTrigger
className={buttonVariants({ variant: "kios" })}
@ -187,7 +199,7 @@ export const KiosMap = () => {
<ul className='flex flex-col gap-4'>
{selectedMaker.stock.map((product, i) => {
return (
<li className="flex flex-row gap-4">
<li className="flex flex-row gap-4" key={product.id}>
{product.picture.url &&
<img
width={160}
@ -202,13 +214,24 @@ export const KiosMap = () => {
<p className='text-black text-lg'
>Weight: {product.weight}</p>
}
<Button
variant={'kios'}
className='w-full mt-6'
onClick={() => handleRequestProduct()}
>
Request product
</Button>
{myself ?
(myRetailers !== undefined) &&
<ul>
{myRetailers.map((retailer, i) => {
return (
<li key={retailer.id}>
<Button
variant="kios"
onClick={() => handleRequestDispatch([product], retailer, selectedMaker)}
>
Request product to {retailer.name}
</Button>
</li>
)
})}
</ul>
: <Button disabled>Login to request</Button>
}
</div>
</li>
)
@ -240,7 +263,7 @@ export const KiosMap = () => {
</h2>
{selectedDispatch.products.map((product, i) => {
return (
<div className='flex flex-row items-center gap-4'>
<div className='flex flex-row items-center gap-4' key={product.id}>
<img
src={product.picture.url}
alt={product.picture.alt}
@ -269,7 +292,6 @@ export const KiosMap = () => {
/>
{selectedDispatch.courier !== undefined ? (
<Contacts
name={selectedDispatch.courier.name}
email={selectedDispatch.courier.email}

View File

@ -15,8 +15,7 @@ import {
import { Input } from "@/components/ui/input"
import axios from "axios";
import { setAuthCookie } from "@/utils/authUtils"
const API_URL = "http://localhost:3001";
import { API_URL } from "@/utils/hooks"
const headers = {
"Content-Type": "application/json",

View File

@ -9,7 +9,7 @@ const buttonVariants = cva(
{
variants: {
variant: {
kios: "text-black border-2 border-gray-950 py-2 px-4 w-full hover:bg-gray-950 transition-all hover:text-white hover:font-bold rounded-none",
kios: "text-black border-2 border-gray-950 py-2 px-4 hover:bg-gray-950 transition-all hover:text-white hover:font-bold rounded-none",
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",

View File

@ -54,10 +54,13 @@ export interface Dispatch {
id: string;
code: string;
products: string[] | Product[];
courier?: string | Courier;
courier?: string | User;
maker: string | Maker;
retailer: string | Retailer;
status: 'requested' | 'accepted' | 'archived';
departure?: string;
arrival?: string;
timeSensitive?: boolean;
updatedAt: string;
createdAt: string;
}

View File

@ -1,21 +1,20 @@
import type { User, Node, Retailer, Maker, Product, Dispatch } from '../astroTypes';
import type { User, Node, Retailer, Maker, Product, Dispatch, CreateDispatch } from '../astroTypes';
import { useQuery, useMutation, useQueryClient, queryOptions } from "@tanstack/react-query";
import axios from "axios";
import { hasAuthCookie } from './authUtils';
import { queryClient } from '@/components/App';
const API_URL = "http://localhost:3001"
export const API_URL = "http://localhost:3001"
const nonAuthHeaders = {
const headers = {
"Content-Type": "application/json",
}
const getMakers = async () => {
const url = `${API_URL}/api/makers`
console.log("Fetching url:", url)
const response = await axios.get(url);
const makers: Maker[] = response.data.docs;
console.log(`Fetch result from ${url}`, makers)
return makers;
}
@ -30,11 +29,9 @@ export const useGetMakers = () => {
const getRetailers = async () => {
const url = `${API_URL}/api/retailers`
console.log("Fetching url:", url)
const response = await axios.get(url);
const retailers: Retailer[] = response.data.docs;
console.log(`Fetch result from ${url}`, retailers)
return retailers;
}
@ -49,11 +46,9 @@ export const useGetRetailers = () => {
const getUser = async (userId: string) => {
const url = `${API_URL}/api/users/${userId}`
console.log("Fetching url:", url)
const response = await axios.get(url);
const user: User = response.data.docs;
console.log(`Fetch result from ${url}`, user)
return user;
}
@ -68,7 +63,6 @@ export const useGetUser = (userId: string) => {
const getMyself = async (authToken: string) => {
const url = `${API_URL}/api/users/me`
console.log("Fetching url:", url)
const authHeaders = {
"Content-Type": "application/json",
@ -81,8 +75,7 @@ const getMyself = async (authToken: string) => {
headers: authHeaders
});
const user: User = response.data
console.log(`Fetch result from ${url}`, user)
const user: User = response.data.user
return user;
}
@ -97,12 +90,9 @@ export const useGetMyself = (authToken: string) => {
const getDispatches = async () => {
const url = `${API_URL}/api/dispatches`
console.log("Fetching url:", url)
const response = await axios.get(url);
const dispatches: Dispatch[] = response.data.docs;
console.log(`Fetch result from ${url}`, dispatches)
return dispatches;
}
@ -113,4 +103,58 @@ export const useGetDispatches = () => {
queryKey: ['dispatches'],
enabled: true
})
}
const createDispatch = async (dispatch: CreateDispatch) => {
const url = `${API_URL}/api/dispatches`;
return await axios.post(url, dispatch);
};
export const useCreateDispatch = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (dispatch: CreateDispatch) => createDispatch(dispatch),
onSuccess: () => {
void queryClient.invalidateQueries({ queryKey: ['dispatches'] });
},
mutationKey: ["createDispatch"]
})
};
const getRetailersByAdminId = async (user: User | undefined) => {
if(user === undefined) {
console.error("getMyRetailers error: user undefined")
return []
}
const adminId = user.id
const url = `${API_URL}/api/retailers`
const response = await axios.get(url);
const retailers: Retailer[] = response.data.docs;
let myRetailers: Retailer[] = []
for (let retailer of retailers) {
console.log(retailer)
if(retailer.admins) {
for (let admin of retailer.admins) {
console.log(admin)
if(admin.id === adminId) {
myRetailers.push(retailer)
}
}
}
}
console.log("myRetailers:", myRetailers)
return myRetailers;
}
export const useGetMyRetailers = (user: User | undefined) => {
return useQuery<Retailer[]>({
queryFn: () => getRetailersByAdminId(user),
queryKey: ['myRetailers'],
enabled: (user !== undefined)
})
}