import React from 'react';
import superagent from 'superagent';
import { fromUnixTime, formatDistanceToNow } from "date-fns";
import { formatInTimeZone } from 'date-fns-tz';
import { DEFAULT_TIMEZONE } from '../CloudApi/Constants.js';
import { Accordion, Header, Message, Icon, Button, Table, Segment, Modal, Statistic, Image, Step, Transition } from 'semantic-ui-react';
import * as ApiUtils from '../CloudApi/ApiUtils.js';
import ApiButtonModal from '../CloudApi/ApiButtonModal.jsx';
import BigShipperWrapper from './BigShipperWrapper.jsx';
import BigOrderComponent from '../BigOrders/BigOrderComponent.jsx';
import HandScannerInput from '../Util/HandScannerInput.jsx';
import BigShipmentInventoryCards from './BigShipmentInventoryCards.jsx';
import BigShipmentLabel from './BigShipmentLabel.jsx';
import BigApiErrorMessage from '../CloudApi/BigApiErrorMessage.jsx';
import InventoryItemsTable from './InventoryItemsTable.jsx';
export default class BigShipment extends React.Component {
constructor(props) {
super(props);
this.state = {
bigShipment: null,
instantLabelPrinting: true, /* Will automatically generate and print the label once all items picked. */
labelTimerSeconds: 2.5,
isLabelPrintTimerActive: false,
showPickingCompleteDialog: false,
infoActiveIndex: 0,
error: null
};
}
async componentDidMount() {
await this.reload();
}
async reload() {
this.setState({ loading: true, error: null, packingLabelScanned: false, packingLabelScanned2: false });
try {
let schemas = await ApiUtils.getFabricatorSchemas();
this.setState({ schemas });
const res = await superagent.get(`/api/admin/shipper/shipment/${this.props.match.params.id}`).accept('json');
let newState = {
bigShipment: res.body.bigShipment,
bigOrder: res.body.bigOrder,
inventoryItems: res.body.inventoryItems,
pickingHints: res.body.pickingHints
}
console.log(res.body);
if (res.body.error) {
newState.bigShipmentError = res.body.error;
}
this.setState(newState);
if (res.body.bigShipment.status === "PrepareShippingLabel") {
this.startLabelPrintTimer();
}
} catch (e) {
console.log(e);
}
this.setState({ loading: false });
}
async onReload() {
await this.reload();
}
renderTestStatus() {
const shopifyOrder = this.state.bigOrder.shopifyOrderSnapshot;
if (shopifyOrder.tags.includes("test order") || shopifyOrder.test === true) {
return (
This is a test order. BE CAREFUL WITH SHIPPING THIS ORDER!
);
}
return null;
}
renderShipmentStatus() {
let segmentColor = "green";
let icon = "warning circle";
// TODO: use a fucking switch
if (this.state.bigShipment.status === "Picking") {
segmentColor = "orange";
} else if (this.state.bigShipment.status === "ReadyToPack") {
segmentColor = "orange";
} else if (this.state.bigShipment.status === "Packing") {
segmentColor = "orange";
} else if (this.state.bigShipment.status === "PreparingToShip") {
segmentColor = "orange";
} else if (this.state.bigShipment.status === "PackedAndLabelled") {
segmentColor = "orange";
} else if (this.state.bigShipment.status === "WaitingForPickup") {
segmentColor = "green";
} else if (this.state.bigShipment.status === "InTransit") {
segmentColor = "orange";
} else if (this.state.bigShipment.status === "Shipped") {
segmentColor = "green";
icon = "checkmark";
} else if (this.state.bigShipment.status === "DeliveryFailed") {
segmentColor = "red";
} else if (this.state.bigShipment.status === "Cancelled") {
segmentColor = "red";
}
// HACK HACK HACK
if (segmentColor == "orange") {
return null;
}
return (
Status: {this.state.bigShipment.status}
);
}
async onScanSerialNumberPicking(scannedSerialNumber) {
if (scannedSerialNumber.startsWith("bs")) {
scannedSerialNumber = scannedSerialNumber.toUpperCase();
}
this.setState({ loading: true, scannedSerialNumber: "" });
try {
const res = await superagent.get(`/api/admin/inventory/items?prefixSerialNumber=${scannedSerialNumber}`);
if (res.body.items.length === 1) {
let inventoryItemId = res.body.items[0].id;
const res2 = await superagent.put(`/api/admin/shipper/shipment/${this.state.bigShipment.id}`)
.send({
action: "PickInventoryItem",
inventoryItemId
});
const audio = new Audio("/misc/Notification3_2.wav");
audio.play();
await this.reload();
if (this.state.instantLabelPrinting === true) {
if (this.state.bigShipment.inventoryItemIds.length === this.state.bigShipment.inventorySlots.length) {
this.setState({ isLabelPrintTimerActive: true });
this.timer = setInterval(this.instantLabelTimerTick.bind(this), 500);
}
}
} else {
console.log("No inventory item found for serial number", scannedSerialNumber);
const audio = new Audio("/misc/Error1.wav");
audio.play();
}
} catch (e) {
console.log(e);
this.setState({ error: e });
}
this.setState({ loading: false });
}
async onAddAccessoryInventoryItem(inventorySlot) {
this.setState({ loading: true });
const res2 = await superagent.put(`/api/admin/shipper/shipment/${this.state.bigShipment.id}`)
.send({
action: "PickInventoryItem",
inventorySlotId: inventorySlot.id,
});
const audio = new Audio("/misc/Notification3_2.wav");
audio.play();
await this.reload();
this.setState({ loading: false });
}
async onPrepareShipping() {
this.setState({ loading: true });
try {
const res = await superagent.put(`/api/admin/shipper/shipment/${this.state.bigShipment.id}`)
.send({
action: "FinishPicking"
});
await this.reload();
} catch (e) {
console.log(e);
}
this.setState({ loading: false });
}
async onGenerateShippingLabel() {
this.setState({ loading: true, error: null });
try {
const res = await superagent.put(`/api/admin/shipper/shipment/${this.state.bigShipment.id}`)
.send({
action: "GenerateShippingLabel"
});
await this.setState({ bigShipment: res.body.shipment });
await this.reload();
} catch (ex) {
this.setState({ error: ex });
console.log(ex);
}
this.setState({ loading: false });
}
async onStartShippingLabelCountdown() {
this.setState({ isLabelPrintTimerActive: true });
this.timer = setInterval(this.instantLabelTimerTick.bind(this), 500);
}
async instantLabelTimerTick() {
if (this.state.isLabelPrintTimerActive && this.state.labelTimerSeconds > 0) {
this.setState({ labelTimerSeconds: this.state.labelTimerSeconds - 0.5 });
} else if (this.state.labelTimerSeconds <= 0) {
clearInterval(this.timer);
await this.onPrepareShipping();
await this.onGenerateShippingLabel();
const audio = new Audio("/misc/Notification24.wav");
audio.play();
}
}
async onStartPacking() {
this.setState({ loading: true });
try {
const res = await superagent.put(`/api/admin/shipper/shipment/${this.state.bigShipment.id}`)
.send({
action: "StartPacking"
});
console.log("onGenerateShippingLabel", res.body);
await this.setState({ bigShipment: res.body.shipment });
await this.reload();
} catch (e) {
console.log(e);
}
this.setState({ loading: false });
}
async onCalculateShippingRates() {
this.setState({ loading: true });
try {
const res = await superagent.put(`/api/admin/shipper/shipment/${this.state.bigShipment.id}`)
.send({
action: "CalculateShippingRates"
});
console.log("onCalculateShippingRates", res.body);
await this.setState({ shippingRates: res.body.shippingRates });
await this.reload();
} catch (e) {
console.log(e);
}
this.setState({ loading: false });
}
async onSelectShippingRate(shippingRate) {
const res = await superagent.put(`/api/admin/shipper/shipment/${this.state.bigShipment.id}`)
.send({
action: "SetShippingRate",
shippingRateId: shippingRate.object_id
});
console.log("onSelectShippingRate", res.body);
await this.setState({ shippingRates: res.body.shippingRates });
await this.reload();
}
async onCancelShipment() {
this.setState({ loading: true, error: null });
try {
const res = await superagent.put(`/api/admin/shipper/shipment/${this.state.bigShipment.id}`)
.send({
action: "Cancel"
});
await this.setState({ bigShipment: res.body.shipment, showCancelShipmentModal: false });
await this.reload();
} catch (ex) {
this.setState({ error: ex });
console.log(ex);
}
this.setState({ loading: false });
}
renderCancelShipmentModal() {
return (
this.setState({ showCancelShipmentModal: false })}
open={this.state.showCancelShipmentModal}>
BE CAREFUL! THIS IS NOT THE USUAL YADA YADA
{this.state.bigShipment.currentShippingLabel && <>
IF A LABEL HAS PRINTED - PLEASE CHECK IF THE LABEL HAS BEEN USED ON SHIPPO OR SHOPIFY.
If the label has been used, you need to reprint the label and scan it in to fulful the shipment here. Do not cancel this shipment if this is the case!
If the label has printed, only an admin can cancel this shipment.
>}
You can safely cancel this shipment if:
The items have not shipped.
There is a shipping label, but it has not been printed or shown as "with carrier".
There is no box waiting to be picked up at the factory.
All items in this shipment must be returned to inventory!
Yes, Cancel Shipment
)
}
renderNextStepButton(text, fn) {
return (
{text}
)
}
renderPickingStage() {
const nextStepButton = this.renderNextStepButton("Next: Print Shipping Label", this.onStartShippingLabelCountdown.bind(this));
let nextStepMessage = (
Ready to print the label!
{nextStepButton}
);
if (this.state.instantLabelPrinting) {
let instaPrintMessage = null;
if (this.state.isLabelPrintTimerActive) {
instaPrintMessage = (
Generating shipping label in {this.state.labelTimerSeconds.toFixed(0)} seconds...
);
} else {
instaPrintMessage = nextStepMessage;
}
return (
<>
{this.state.bigShipment.inventoryItemIds.length} of {this.state.bigShipment.inventorySlots.length} Items Scanned
{this.state.bigShipment.inventoryItemIds.length === this.state.bigShipment.inventorySlots.length ?
instaPrintMessage :
}
>
);
} else {
return (
<>
{this.state.bigShipment.inventoryItemIds.length} of {this.state.bigShipment.inventorySlots.length} Items Scanned
{this.state.bigShipment.inventoryItemIds.length === this.state.bigShipment.inventorySlots.length ?
nextStepMessage :
}
>
);
}
}
startLabelPrintTimer() {
this.timer = setInterval(this.nextLabelTimerTick.bind(this), 1000);
}
nextLabelTimerTick() {
if (this.state.isLabelPrintTimerActive && this.state.labelTimerSeconds > 0) {
this.setState({ labelTimerSeconds: this.state.labelTimerSeconds - 1 });
} else if (this.state.labelTimerSeconds === 0) {
this.setState({ isLabelPrintTimerActive: false, showPickingCompleteDialog: true });
clearInterval(this.timer);
}
}
renderPickingCompleteDialog() {
return (
this.setState({ showPickingCompleteDialog: false })}
open={this.state.showPickingCompleteDialog}>
Place the newly printed shipping label into the picking container.
Move the picking container to the packing queue.
DONE, GET ME THE NEXT ORDER
START PACKING
)
}
onExpandInfo = (e, titleProps) => {
const { index } = titleProps
const { infoActiveIndex } = this.state
const newIndex = infoActiveIndex === index ? -1 : index
this.setState({ infoActiveIndex: newIndex })
}
renderShippingLabel() {
if (this.state.bigShipment && this.state.bigShipment.currentShippingLabel) {
let shippingLabel = this.state.bigShipment.currentShippingLabel;
const nextStepButton = this.renderNextStepButton("Next: Start Packing", this.onStartPacking.bind(this));
return (
<>
{nextStepButton}
{this.state.isLabelPrintTimerActive &&
Printing Label in {this.state.labelTimerSeconds} seconds...
}
{this.renderPickingCompleteDialog()}
{nextStepButton}
>
);
} else {
const nextStepButton = this.renderNextStepButton("Next: Generate Shipping Label", this.onGenerateShippingLabel.bind(this));
return (
<>
{nextStepButton}
>
);
}
}
renderShippingRateButton() {
return (
Calculate Shipping Rates
);
}
renderShippingRates() {
if (this.state.shippingRates) {
return (
<>
{this.state.shippingRates.rates.map((shippingRate, shippingRateIndex) => {
return (
{shippingRate.provider} {shippingRate.servicelevel && shippingRate.servicelevel.name}
{shippingRate.duration_terms}
{shippingRate.amount}
{shippingRate.currency}
Select {shippingRate.provider} {shippingRate.servicelevel && shippingRate.servicelevel.name}
);
})}
>
);
}
}
async onScanShippingLabel(scannedTrackingNumber) {
this.setState({ loading: true, scannedTrackingNumber: "" });
try {
console.log(scannedTrackingNumber);
const res = await superagent.get(`/api/admin/shipper/shipments?trackingNumber=${_.toUpper(scannedTrackingNumber)}`);
if (res.body.items.length > 0 && res.body.items[0].id === this.state.bigShipment.id) {
this.setState({ packingLabelScanned: true });
/*const res2 = await superagent.put(`/api/admin/shipper/shipment/${this.state.bigShipment.id}`)
.send({
action: "ScanLabelTrackingNumber",
scannedTrackingNumber
});
*/
const audio = new Audio("/misc/Notification3_2.wav");
audio.play();
} else {
this.setState({ error: "Couldn't find label" });
const audio = new Audio("/misc/Error1.wav");
audio.play();
}
} catch (e) {
const audio = new Audio("/misc/Error1.wav");
audio.play();
this.setState({ error: e });
console.log(e);
}
this.setState({ loading: false });
}
async onPackingFinished() {
const res = await superagent.put(`/api/admin/shipper/shipment/${this.state.bigShipment.id}`)
.send({
action: "FinishPacking"
});
await this.reload();
}
renderPackingStage() {
return (
<>
1. Scan the shipping label
{this.state.packingLabelScanned && this.renderNextStepButton("Yes, I have completed packing the items", this.onPackingFinished.bind(this))}
{this.state.packingLabelScanned && this.renderNextStepButton("Yes, I have completed packing the items", this.onPackingFinished.bind(this))}
>
);
}
async onScanFinalShippingLabel(scannedTrackingNumber) {
this.setState({ loading: true, scannedTrackingNumber: "" });
try {
const res = await superagent.get(`/api/admin/shipper/shipments?trackingNumber=${_.toUpper(scannedTrackingNumber)}`);
if (res.body.items.length > 0 && res.body.items[0].id === this.state.bigShipment.id) {
this.setState({ packingLabelScanned2: true });
const audio = new Audio("/misc/Notification3_2.wav");
audio.play();
} else {
this.setState({ error: "Couldn't find label" });
const audio = new Audio("/misc/Error1.wav");
audio.play();
}
} catch (e) {
const audio = new Audio("/misc/Error1.wav");
audio.play();
this.setState({ error: e });
console.log(e);
}
this.setState({ loading: false });
}
async onRegenerateShippingLabel() {
const res = await superagent.put(`/api/admin/shipper/shipment/${this.state.bigShipment.id}`)
.send({
action: "RegenerateShippingLabel"
});
await this.reload();
}
async onForceStatusUpdate() {
const res = await superagent.put(`/api/admin/shipper/shipment/${this.state.bigShipment.id}`)
.send({
action: "ForceWaitingForPickup"
});
await this.reload();
}
async onScanSerialNumberRemachineCushion(scannedSerialNumber) {
if (scannedSerialNumber.startsWith("bs")) {
scannedSerialNumber = scannedSerialNumber.toUpperCase();
}
const res = await superagent.get(`/api/admin/inventory/items?serialNumber=${scannedSerialNumber}`);
if (res.body.items.length === 1) {
this.setState({ loading: true, remachinedCushionInventoryItem: res.body.items[0] });
}
this.setState({ loading: false });
}
async onConfirmRemachineCushion() {
if (this.state.remachinedCushionInventoryItem) {
// Remachine the cushion item
this.setState({ loading: true, error: null });
let res = await superagent.put(`/api/admin/shipper/shipment/${this.state.bigShipment.id}`)
.send({
inventoryItemId: this.state.remachinedCushionInventoryItem.id,
action: "RemachineCushion"
});
this.setState({ bigShipment: res.body.shipment, loading: false, remachinedCushionInventoryItem: null });
}
}
renderShippingStage() {
if (this.state.packingLabelScanned2 === true) {
return (
🎉🎉🎉🎉🎉🎉🎉🎉🎉
Shipment is now ready for fulfillment
);
} else {
return (
Final step! Scan the shipping label on the box to move to fulfillment
);
}
}
renderWaitingForPickup() {
return (
);
}
renderSteps() {
const steps = [
{
status: "Picking",
icon: "find",
title: "Picking",
description: "Collect items from inventory"
},
{
status: "PrepareShippingLabel",
icon: "print",
title: "Print Shipping Label",
description: "Print Shipping Label"
},
{
status: "Packing",
icon: "box",
title: "Packing",
description: "Pack items and seal"
},
{
status: "PreparingToShip",
icon: "truck",
title: "Preparing To Ship",
description: "Final check"
},
{
status: "WaitingForPickup",
icon: "box",
title: "Waiting For Pickup",
description: "Waiting for pickup from carrier"
}
];
const uiSteps = [];
for (let i = 0; i < steps.length; i++) {
let step = steps[i];
let active = this.state.bigShipment.status === step.status;
// Completed if the index of status less than the current step index
let completed = _.findIndex(steps, (s) => s.status === this.state.bigShipment.status) > i;
let disabled = _.findIndex(steps, (s) => s.status === this.state.bigShipment.status) < i;
uiSteps.push(
{step.title}
{step.description}
);
}
return (
{uiSteps}
);
}
render() {
if (this.state.bigShipment) {
const shipmentHistory = (
When
User
Message
{this.state.bigShipment.history.map((historyItem, historyItemIndex) => {
let createdAtDateTime = fromUnixTime(historyItem.createdAt / 1000);
const createdAt = formatInTimeZone(createdAtDateTime, DEFAULT_TIMEZONE, 'PPPp z');
return (
{createdAt}
{historyItem.creatorBigscreenAccountId}
{historyItem.message}
{(historyItem.metaData && historyItem.metaData.reason) && Reason: {historyItem.metaData.reason}
}
)
})}
);
let createdAtDateTime = fromUnixTime(this.state.bigShipment.createdAt / 1000);
const createdAt = formatInTimeZone(createdAtDateTime, DEFAULT_TIMEZONE, 'PPPp z');
const fromNow = formatDistanceToNow(createdAtDateTime, { addSuffix: true });
const title = (
{`🚢 Shipment ${this.state.bigShipment.id}`}
Created: {createdAt} ({fromNow})
);
let body;
if (this.state.bigShipmentError) {
body = ;
} else {
body = (
<>
{this.renderSteps()}
{this.state.bigShipment.status === "Picking" && this.renderPickingStage()}
{this.state.bigShipment.status === "PrepareShippingLabel" && this.renderShippingLabel()}
{this.state.bigShipment.status === "Packing" && this.renderPackingStage()}
{this.state.bigShipment.status === "PreparingToShip" && this.renderShippingStage()}
{this.state.bigShipment.status === "WaitingForPickup" && this.renderWaitingForPickup()}
>
);
}
return (
<>
{this.renderCancelShipmentModal()}
{title}
{this.renderTestStatus()}
{this.renderShipmentStatus()}
For Order: 📦 {this.state.bigOrder.shopifyOrderName}
Items ({this.state.bigShipment.inventoryItemIds.length})
Shipment History ({this.state.bigShipment.history.length} events)
{shipmentHistory}
{this.state.bigShipment.currentShippingLabel &&
Shipping Label
}
{this.state.bigShipment.currentShippingLabel &&
Are you sure you want to regenerate this shipping label? A new label will cost us money!
Note: For now, the previous shipping label will not automatically be cancelled - please destroy in Shippo.
}
{body}
{(this.state.bigShipment.inventorySlots.find(item => ["CustomCushion", "BeyondCushionV1"].includes(item.productType))) &&
Re-machine Cushion
{ this.setState({ remachinedCushionInventoryItem: null }); }).bind(this)}
onConfirm={this.onConfirmRemachineCushion.bind(this)}>
{(this.state.remachinedCushionInventoryItem)
?
Found!
Re-machining cushion for inventory item: {this.state.remachinedCushionInventoryItem.serialNumber}
:
}
This will force the cushion to be re-machined with a priority of 99.
}
{(this.state.bigShipment.status === "Packing" || this.state.bigShipment.status === "PreparingToShip") &&
Force Status Update
This force updates the shipment status to "Waiting For Pickup" - use with caution!
This will only work if the underlying items in the shopify order(s) have all been fulfilled.
}
Cancel shipment
this.setState({ showCancelShipmentModal: true })} size="small">Cancel Shipment
>
);
} else {
return null;
}
}
}