reactjs - Iterează printr-un răspuns API în next js
Lucrez în prezent la un proiect în care trebuie să imprim niște etichete printr-un clic pe un buton. Totuși, etichetele pot fi tipărite doar dacă sunt îndeplinite anumite cerințe.
Mai întâi, utilizatorul meu ajunge la un dialog ShadCn unde poate bifa niște căsuțe. Căsuțele bifează trimit apoi o cerere către o funcție numită checkFulfilled (care va fi prezentată mai jos). Acea funcție verifică dacă un articol este onorat în comenzi sau nu. Dacă nu este, trimite o cerere POST pentru a-l onora. Apoi, din acea funcție, transmit informații către o altă funcție numită createSale care sunt necesare pentru a rezerva expedierea și pentru a marca starea de onorare în API. Fără acea stare de onorare, eticheta nu poate fi tipărită.
Acum urmează partea interesantă. Când comanda este îndeplinită, API-ul va returna întreaga comandă, dar doar o parte a comenzii este importantă—aceasta se numește order_fulfillments, care poate avea între 2 și 1000 de array-uri în ea. Fiecare array din order_fulfillments are un așa-numit shipment_id, dar doar unul dintre shipment_id-uri nu este null. Trebuie să parcurg toate ID-urile de livrare înainte să pot tipări eticheta pentru că API-ul printLabel necesită să trimit shipment_id-ul.
Problema: În acest moment, când încerc să imprim eticheta, chiar și atunci când totul altceva a trecut cu succes, primesc acest mesaj de eroare: „shipmentId este nul, nu se apelează PrintLabel.” Poate cineva să explice cum pot rezolva problema?
Codul:
Componenta de dialog unde are loc toată interacțiunea frontend
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Separator } from "@/components/ui/separator";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Package } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Orders } from "@/lib/types/shipmondo";
import { FurfillOrder, PrintLabel, cancel, createSale, findId, putStatus } from "./actions";
import { Product } from "@/lib/types/wix";
import { Button } from "@/components/ui/button";
import { Progress } from "@/components/ui/progress";
import { Checkbox } from "@/components/ui/checkbox";
import { CircleX } from "lucide-react";
import { string } from "zod";
export default function Dialogt({ order, totalWeight, products }: { order: Orders, totalWeight: number, products: Product[] }) {
const router = useRouter();
// const [isOpen, setIsOpen] = useState(true);
const [checkedItems, setCheckedItems] = useState<number[]>([]);
const [isPacking, setIsPacking] = useState(false);
const furfillOrder = async (item_id: string, order_id: string, order: Orders, item_quantity: Number) => {
await FurfillOrder(item_id, order_id, item_quantity);
}
const handleCheckboxChange = (index: number, item_id: string, order_id: string, item_quantity: Number) => {
setCheckedItems(prevCheckedItems =>
prevCheckedItems.includes(index)
? prevCheckedItems.filter(item => item !== index)
: [...prevCheckedItems, index]
);
furfillOrder(item_id, order_id, order, item_quantity);
};
const calculateProgress = () => {
return (checkedItems.length / order.order_lines.length) * 100;
};
// const packad = async (order_id: string) => {
// await putStatus(order_id);
// setIsPacking(true);
// };
// const handleClose = () => {
// setIsOpen(false);
// };
// const cancelOrder = async (order_id: string) => {
// await cancel(order_id);
// handleClose();
// };
const print = async (order_id: any) => {
await PrintLabel(order_id);
}
const ida = async (order_id: string) =>{
await findId(order_id)
}
useEffect(() => {
if (isPacking) {
router.push('/packning/packas');
}
}, [isPacking, router]);
return (
<Dialog>
<DialogTrigger>
<Package size={25} />
</DialogTrigger>
<DialogContent className={'g:max-w-screen-lg overflow-y-scroll max-h-screen'}>
<DialogHeader>
<DialogTitle className="items-center flex justify-around">
Order {order.order_id.substring(0, 12)}
{/* {order.order_status === "on_hold" ? <Badge className="bg-[#ffA500] text-black">Påbörjad</Badge> : <Button onClick={() => packad(order.id)}>Packa</Button>}
{order.order_status === 'on_hold' ? <Button
onClick={() => cancelOrder(order.id)}
variant={'ghost'} color={'destructive'}>Avbryt
</Button> : ''} */}
</DialogTitle>
</DialogHeader>
<DialogDescription>
<Card key={order.id} className="flex items-center p-6 rounded-lg shadow-lg m-4">
<div className="flex flex-col items-start gap-4">
<div className="flex flex-col items-start gap-1">
<h3 className=" font-bold">
{order.ship_to.name}
</h3>
<div className="flex items-center gap-2 text-muted-foreground">
<span>Id: {order.order_id.substring(0, 12)}</span>
<div className="h-3 w-3 rounded-full bg-muted" />
<span>Vikt: {totalWeight.toFixed(2)}kg</span>
</div>
</div>
<div className="flex items-center gap-4">
{order.order_status === "open" ? <Badge variant={'destructive'}>Ska packas</Badge> : <Badge className="bg-[#ffA500] text-black">Påbörjad</Badge>}
<div className="text-base">Artiklar: {`${order.order_lines.length - 1}`}</div>
</div>
</div>
</Card>
{order.order_note && order.order_note.length > 1 ? (
<Card key={order.id} className="flex p-6 rounded-lg shadow-lg m-4">
<h2 className="w-full">
{order.order_note}
</h2>
</Card>
) : (
<Card key={order.id} className="flex p-6 rounded-lg shadow-lg m-4">
<h2 className="w-full">
inga anteckningar
</h2>
</Card>
)}
</DialogDescription>
<Separator />
<DialogHeader>
<DialogTitle className="m-2">Produkter</DialogTitle>
<Progress className="sticky top-4 shadow-2xl" value={calculateProgress()} />
{order.order_lines.map((line: any, index: number) => (
<Card key={index} className="flex items-center p-6 rounded-lg shadow-lg m-4">
<div className="flex flex-col items-start gap-4 w-full">
<div className="flex flex-col items-start gap-1">
<h3 className=" font-bold">
{line.item_name}
</h3>
<div className="flex items-center gap-2 text-muted-foreground">
<span>Id: {line.id}</span>
<div className="h-3 w-3 rounded-full bg-muted" />
<span>Vikt: {line.unit_weight}gr</span>
</div>
</div>
<div className="flex items-center gap-4 font-bold ">
<div>{Math.floor(line.quantity)} st</div>
</div>
</div>
<div className="ml-auto">
<Checkbox
checked={checkedItems.includes(index)}
onClick={() => handleCheckboxChange(index, line.id, order.id, line.quantity)}
/>
</div>
</Card>
))}
{order.fulfillment_status === "fulfilled" || "partially_fulfilled" ?
<Button className="bg-[#00FF00] text-black"
onClick={()=> ida()}>
Printa label
</Button> : ''
}
</DialogHeader>
</DialogContent>
</Dialog>
)
}
Backend-ul unde apelez toate funcțiile print, find și așa mai departe pentru API
"use server"
import { Orders } from "@/lib/types/shipmondo"
import { Product } from "@/lib/types/wix";
import { revalidatePath } from 'next/cache'
export const putStatus = async (order_id: string) => {
const url = "https://app.shipmondo.com/api/public/v3/sales_orders/" + order_id;
const options = {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: 'Basic *********=='
},
body: '{"order_status":"on_hold"}'
};
const resp = await fetch(url, options);
const data = await resp.json();
console.log(data.order_status, data.id);
revalidatePath('/packning/packas')
revalidatePath('/packning')
return data
}
// export const cancel = async (order_id: string) => {
// const url = "https://app.shipmondo.com/api/public/v3/sales_orders/" + order_id;
// const options = {
// method: 'PUT',
// headers: {
// 'Content-Type': 'application/json',
// Accept: 'application/json',
// Authorization: 'Basic *********=='
// },
// body: '{"order_status":"open"}'
// };
// const resp = await fetch(url, options);
// const data = await resp.json();
// revalidatePath('/packning/packas')
// revalidatePath('/packning')
// return data
// }
const checkFurfilled = async (order_id: string) => {
const url = `https://app.shipmondo.com/api/public/v3/sales_orders/` + order_id;
const options = {
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: 'Basic *******=='
}
}
const response = await fetch(url, options);
const data = await response.json();
await findId(order_id); // <--- added await here
return data;
}
export const createSale = async (order_id: string) => {
const url = `https://app.shipmondo.com/api/public/v3/sales_orders/${order_id}/create_shipment`;
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: 'Basic *********=='
},
}
const fulfillmentStatus = await checkFurfilled(order_id);
console.log(fulfillmentStatus.fulfillment_status)
if (fulfillmentStatus.fulfillment_status === "fulfilled") {
const resp = await fetch(url, options);
const data = await resp.json();
console.log("allt gick genom")
return data;
} else {
console.error("Error: Något gick fel. Order ID:", order_id, "Fulfillment Status:", fulfillmentStatus.fulfillment_status)
}
}
export const FurfillOrder = async (item_id: string, order_id: string, item_quantity: Number) => {
const url = `https://app.shipmondo.com/api/public/v3/sales_orders/${order_id}/fulfillments`;
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: 'Basic *****=='
},
body: `{"fulfillment_lines":[{"order_line_id":${item_id},"shipped_quantity":${item_quantity}}]}`
};
try {
const response = await fetch(url, options);
const data = await response.json();
createSale(order_id);
} catch (error) {
console.error(error);
}
};
export const findId = async (order_id: string) => {
const url = `https://app.shipmondo.com/api/public/v3/sales_orders/` + order_id;
const options = {
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: 'Basic *******=='
}
}
const response = await fetch(url, options);
const data = await response.json();
let shipmentId = null;
data?.order_fulfillments?.forEach((fulfillment: any) => {
if (fulfillment.shipment_id !== null) {
shipmentId = fulfillment.shipment_id;
}
});
if (shipmentId !== null) {
console.log('Calling PrintLabel with shipmentId:', shipmentId);
await PrintLabel(shipmentId); // <--- added await here
} else {
console.log('shipmentId is null, not calling PrintLabel');
}
return shipmentId;
};
export const PrintLabel = async (shipmentId: number) => {
const url = 'https://app.shipmondo.com/api/public/v3/print_jobs';
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: 'Basic ******=='
},
body: `{"document_id":${shipmentId},"document_type":"shipment","host_name":"IT50462","printer_name":"ZDesigner GX420d","label_format":"zpl"}`
};
console.log(shipmentId)
try {
const response = await fetch(url, options);
const data = await response.json();
console.log(data);
console.log("due gud")
} catch (error) {
console.error(error);
console.log("något gick fel")
}
}
răspunsul API pe care îl primesc când încerc să găsesc shipment_id
{
"id": 85466962,
"order_id": "34952",
"reference": "34952",
"ordered_at": "2024-07-31T09:25:46.034+02:00",
"order_status": "packed",
"fulfillment_status": "fulfilled",
"payment_status": null,
"shipped_percent": 100,
"fulfilled_percent": 100,
"archived": false,
"source_name": "**** / ***** AB",
"order_note": null,
"created_at": "2024-07-31T09:29:47.992+02:00",
"updated_at": "2024-07-31T09:50:50.001+02:00",
"ship_to": {
"name": "**** ",
"attention": null,
"address1": "****",
"address2": null,
"zipcode": "463 94",
"city": "****",
"country_code": "SE",
"email": "****",
"mobile": "***",
"phone": "****",
"instruction": null,
"vat_id": null,
"address_validation": {
"state": null,
"suggestion": null
}
},
"bill_to": {
"name": "*** ",
"attention": null,
"address1": "****",
"address2": null,
"zipcode": "****",
"city": "****",
"country_code": "SE",
"email": "****",
"mobile": null,
"phone": null
},
"service_point": {
"id": null,
"name": null,
"address1": null,
"zipcode": null,
"city": null,
"country_code": null,
"shipping_agent": null
},
"sender": {
"name": "****",
"address1": "****",
"address2": null,
"zipcode": "****",
"city": "****",
"country_code": "SE",
"email": "****",
"telephone": "+****",
"mobile": "+****"
},
"payment_details": {
"amount_excluding_vat": "266.96",
"amount_including_vat": "299.0",
"authorized_amount": "299.0",
"captured_amount": null,
"refunded_amount": null,
"currency_code": "SEK",
"vat_amount": "32.04",
"vat_percent": "0.12",
"payment_method": "Klarna",
"transaction_id": "****"
},
"order_lines": [
{
"id": 280411112,
"line_type": "shipping",
"item_name": "Postnord Varubrev 1:a klass",
"item_sku": "shipping",
"item_variant_code": null,
"item_barcode": null,
"quantity": "1.0",
"unit_price_excluding_vat": "0.0",
"discount_amount_excluding_vat": "0.0",
"amount_excluding_vat": "0.0",
"amount_including_vat": "0.0",
"vat_amount": "0.0",
"vat_percent": "0.12",
"currency_code": "SEK",
"shipped_quantity": "0.0",
"unit_weight": null,
"cost_price": null,
"country_code_of_origin": null,
"customs_code": null,
"customs_description": null,
"image_url": null,
"item_bin": null,
"item_id": 8784123
},
{
"id": 280411113,
"line_type": "item",
"item_name": "****",
"item_sku": "****r",
"item_variant_code": null,
"item_barcode": null,
"quantity": "1.0",
"unit_price_excluding_vat": "266.964285714285714285714285715",
"discount_amount_excluding_vat": "0.0",
"amount_excluding_vat": "266.964285714285714285714285715",
"amount_including_vat": "299.0000000000000000000000000008",
"vat_amount": "32.0357142857142857142857142858",
"vat_percent": "0.12",
"currency_code": "SEK",
"shipped_quantity": "1.0",
"unit_weight": 700,
"cost_price": null,
"country_code_of_origin": null,
"customs_code": null,
"customs_description": null,
"image_url": null,
"item_bin": null,
"item_id": 9382141
}
],
"order_fulfillments": [
{
"id": 31723354,
"code": "34952-2",
"type": "outbound",
"shipment_id": null,
"fulfilled_by_third_party": false,
"order_packaging_id": null,
"cancelled": true,
"created_at": "2024-07-31T09:32:16.342+02:00",
"updated_at": "2024-07-31T09:32:40.039+02:00",
"fulfillment_lines": [
{
"id": 74019921,
"shipped_quantity": "1.0",
"order_line_id": 280411113
}
]
},
{
"id": 31723343,
"code": "34952-1",
"type": "outbound",
"shipment_id": null,
"fulfilled_by_third_party": false,
"order_packaging_id": null,
"cancelled": false,
"created_at": "2024-07-31T09:32:05.911+02:00",
"updated_at": "2024-07-31T09:32:05.911+02:00",
"fulfillment_lines": [
{
"id": 74019904,
"shipped_quantity": "1.0",
"order_line_id": 280411112
}
]
},
{
"id": 31724023,
"code": "34952-3",
"type": "outbound",
"shipment_id": null,
"fulfilled_by_third_party": false,
"order_packaging_id": null,
"cancelled": false,
"created_at": "2024-07-31T09:38:15.468+02:00",
"updated_at": "2024-07-31T09:38:15.468+02:00",
"fulfillment_lines": [
{
"id": 74021506,
"shipped_quantity": "1.0",
"order_line_id": 280411112
}
]
},
{
"id": 31725072,
"code": "34952-6",
"type": "outbound",
"shipment_id": 322705390,
"fulfilled_by_third_party": false,
"order_packaging_id": null,
"cancelled": false,
"created_at": "2024-07-31T09:50:49.963+02:00",
"updated_at": "2024-07-31T09:50:51.143+02:00",
"fulfillment_lines": [
{
"id": 74023792,
"shipped_quantity": "1.0",
"order_line_id": 280411113
}
]
},
{
"id": 31725067,
"code": "34952-5",
"type": "outbound",
"shipment_id": null,
"fulfilled_by_third_party": false,
"order_packaging_id": null,
"cancelled": false,
"created_at": "2024-07-31T09:50:46.604+02:00",
"updated_at": "2024-07-31T09:50:46.604+02:00",
"fulfillment_lines": [
{
"id": 74023781,
"shipped_quantity": "1.0",
"order_line_id": 280411112
}
]
},
{
"id": 31724025,
"code": "34952-4",
"type": "outbound",
"shipment_id": null,
"fulfilled_by_third_party": false,
"order_packaging_id": null,
"cancelled": true,
"created_at": "2024-07-31T09:38:18.958+02:00",
"updated_at": "2024-07-31T09:44:30.202+02:00",
"fulfillment_lines": [
{
"id": 74021510,
"shipped_quantity": "1.0",
"order_line_id": 280411113
}
]
}
],
"enable_customs": false,
"shipment_template_id": 710296,
"return_shipment_template_id": null,
"sales_order_packaging_id": null,
"bookkeeping_integration_id": null,
"use_item_weight": true,
"carrier_fields": null,
"assigned_staff_account_id": null
}
nu ezitați să cereți informații suplimentare