Complete basic request dispatch flow
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
@ -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;
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user