Admin can now view and print shipping label
parent
2e2ff356c2
commit
24a6c6fee1
@ -0,0 +1,33 @@
|
||||
import {useState, useEffect} from 'react'
|
||||
|
||||
const init = {defined: false}
|
||||
|
||||
export default function useLocalStorage(key, defaultValue) {
|
||||
const [state, setState] = useState(init)
|
||||
|
||||
// Load from localstorage on first render or if key changes
|
||||
useEffect(() => {
|
||||
const stored = window.localStorage.getItem(key)
|
||||
|
||||
try {
|
||||
if(!stored) throw new Error('No stored value')
|
||||
setState(JSON.parse(stored))
|
||||
} catch {
|
||||
setState(defaultValue)
|
||||
}
|
||||
}, [key])
|
||||
|
||||
// Persist to localstorage when state changes
|
||||
useEffect(() => {
|
||||
if(state === init) {
|
||||
return
|
||||
}
|
||||
|
||||
window.localStorage.setItem(key, JSON.stringify(state))
|
||||
}, [state])
|
||||
|
||||
if(state === init)
|
||||
return [defaultValue, setState]
|
||||
|
||||
return [state, setState];
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
import React, {useState, useRef, useEffect} from 'react'
|
||||
import {DateTime} from 'luxon'
|
||||
import {Button} from '~/components/form'
|
||||
|
||||
import styles from '~/styles/shippingLabel.module.css'
|
||||
|
||||
import useLocalStorage from '~/hooks/useLocalStorage'
|
||||
|
||||
OrderLabel.getInitialProps = async ({ctx: {axios, query: {id}}}) => {
|
||||
const {data: order} = await axios.get(`/api/orders/${id}`)
|
||||
return {order}
|
||||
}
|
||||
|
||||
export default function OrderLabel({order}){
|
||||
const [printCache, setPrintCache] = useLocalStorage('admin:orderlabelview', {})
|
||||
const [imgLoaded, setImgLoaded] = useState(false)
|
||||
const imgRef = useRef()
|
||||
|
||||
const currentRotation = parseInt(printCache[order.uuid]?.rotation || '0deg', 10)
|
||||
|
||||
// On first load, if we haven't already decided rotation for this image,
|
||||
// determine based on aspect ratio
|
||||
useEffect(() => {
|
||||
if(printCache[order.uuid] || !imgRef.current || !imgLoaded)
|
||||
return;
|
||||
|
||||
const {current: img} = imgRef
|
||||
|
||||
if(img.naturalWidth > img.naturalHeight)
|
||||
setRotation(90)
|
||||
|
||||
}, [printCache, imgLoaded, imgRef.current])
|
||||
|
||||
function setRotation(deg) {
|
||||
setPrintCache(printCache => {
|
||||
return clearCache({
|
||||
...printCache,
|
||||
[order.uuid]: {
|
||||
uuid: order.uuid,
|
||||
rotation: deg + 'deg',
|
||||
timestamp: DateTime.local().toISO()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function rotateRight() {
|
||||
setRotation((currentRotation + 90) % 360)
|
||||
}
|
||||
|
||||
function rotateLeft() {
|
||||
setRotation((currentRotation - 90) % 360)
|
||||
}
|
||||
|
||||
function print() {
|
||||
printLabel(imgRef.current, currentRotation)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>Print Shipping Label</h2>
|
||||
<div className={styles.controls}>
|
||||
<Button outline icon="rotate_left" onClick={rotateLeft}/>
|
||||
<Button outline icon="print" onClick={print}/>
|
||||
<Button outline icon="rotate_right" onClick={rotateRight}/>
|
||||
</div>
|
||||
<div className={styles.container}>
|
||||
<img ref={imgRef} onLoad={() => setImgLoaded(true)} style={{transform: `rotate(${currentRotation}deg)`}} src={order.delivery.easypost.postage_label.label_url}/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
// Clear out older cache entries . . . return cleared cache
|
||||
function clearCache(cache) {
|
||||
// Get all cache values
|
||||
return Object.values(cache)
|
||||
// Filter out anything older than a week
|
||||
.filter(({timestamp}) => Math.abs(DateTime.fromISO(timestamp).diffNow().as('days')) < 7)
|
||||
// Convert back to keyed object
|
||||
.reduce((acc, obj) => ({...acc, [obj.uuid]: obj}), {})
|
||||
}
|
||||
|
||||
function printLabel(image, rotationDegrees) {
|
||||
const {naturalWidth: width, naturalHeight: height} = image
|
||||
const shorterSize = width > height ? 'height' : 'width'
|
||||
const longerSize = width > height ? 'width' : 'height'
|
||||
|
||||
const {origin, translate} = (() => {
|
||||
switch(rotationDegrees) {
|
||||
case 90:
|
||||
case -270:
|
||||
return {origin: 'left top', translate: '0%, -100%'}
|
||||
|
||||
case 180:
|
||||
case -180:
|
||||
return {origin: 'center center', translate: '0%, 0%'}
|
||||
|
||||
case 270:
|
||||
case -90:
|
||||
return {origin: 'left top', translate:'-100%, 0%'}
|
||||
|
||||
default:
|
||||
return {origin: 'left top', translate: '0%, 0%'}
|
||||
}
|
||||
})()
|
||||
|
||||
|
||||
const popup = window.open("", "Print label", "toolbar=off,width=800,height=600")
|
||||
popup.document.write(`<img src="${image.getAttribute('src')}"/>`)
|
||||
popup.document.write(`
|
||||
<style>
|
||||
html, body { width: 100%; margin: 0; padding: 0;}
|
||||
|
||||
img {
|
||||
${shorterSize}: 4in;
|
||||
${longerSize}: auto;
|
||||
transform-origin: ${origin};
|
||||
transform: rotate(${rotationDegrees}deg) translate(${translate});
|
||||
}
|
||||
</style>
|
||||
`)
|
||||
popup.document.close()
|
||||
popup.focus()
|
||||
popup.print()
|
||||
popup.close()
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
.label {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
max-height: 400px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
max-height: 600px;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.container > img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.controls {
|
||||
text-align: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.controls > div {
|
||||
display: inline-block;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.controls > div button {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
padding: 10px 8px 6px 8px;
|
||||
}
|
Loading…
Reference in New Issue