feat: show company logo on receipt if uploaded, fall back to name
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { queryOptions } from '@tanstack/react-query'
|
||||
import { api } from '@/lib/api-client'
|
||||
import { usePOSStore } from '@/stores/pos.store'
|
||||
import JsBarcode from 'jsbarcode'
|
||||
|
||||
interface ReceiptLineItem {
|
||||
@@ -47,10 +51,51 @@ interface POSReceiptProps {
|
||||
footerText?: string
|
||||
}
|
||||
|
||||
function useStoreLogo(companyId?: string) {
|
||||
const token = usePOSStore((s) => s.token)
|
||||
const [logoSrc, setLogoSrc] = useState<string | null>(null)
|
||||
|
||||
const { data: storeData } = useQuery(queryOptions({
|
||||
queryKey: ['store'],
|
||||
queryFn: () => api.get<{ id: string }>('/v1/store'),
|
||||
enabled: !!token,
|
||||
}))
|
||||
|
||||
const storeId = companyId ?? storeData?.id
|
||||
const { data: filesData } = useQuery(queryOptions({
|
||||
queryKey: ['files', 'company', storeId ?? ''],
|
||||
queryFn: () => api.get<{ data: { id: string; path: string }[] }>('/v1/files', { entityType: 'company', entityId: storeId }),
|
||||
enabled: !!storeId,
|
||||
}))
|
||||
|
||||
const logoFile = filesData?.data?.find((f) => f.path.includes('/logo-'))
|
||||
|
||||
useEffect(() => {
|
||||
if (!logoFile || !token) { setLogoSrc(null); return }
|
||||
let cancelled = false
|
||||
let blobUrl: string | null = null
|
||||
async function load() {
|
||||
try {
|
||||
const res = await fetch(`/v1/files/serve/${logoFile!.path}`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
})
|
||||
if (!res.ok || cancelled) return
|
||||
const blob = await res.blob()
|
||||
if (!cancelled) { blobUrl = URL.createObjectURL(blob); setLogoSrc(blobUrl) }
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
load()
|
||||
return () => { cancelled = true; if (blobUrl) URL.revokeObjectURL(blobUrl) }
|
||||
}, [logoFile?.path, token])
|
||||
|
||||
return logoSrc
|
||||
}
|
||||
|
||||
export function POSReceipt({ data, size = 'thermal', footerText }: POSReceiptProps) {
|
||||
const barcodeRef = useRef<SVGSVGElement>(null)
|
||||
const { transaction: txn, company, location } = data
|
||||
const isThermal = size === 'thermal'
|
||||
const logoSrc = useStoreLogo()
|
||||
|
||||
useEffect(() => {
|
||||
if (barcodeRef.current) {
|
||||
@@ -84,7 +129,11 @@ export function POSReceipt({ data, size = 'thermal', footerText }: POSReceiptPro
|
||||
>
|
||||
{/* Store header */}
|
||||
<div className="text-center pb-2 border-b border-dashed border-gray-400">
|
||||
<div className={`font-bold ${isThermal ? 'text-sm' : 'text-lg'}`}>{company.name}</div>
|
||||
{logoSrc ? (
|
||||
<img src={logoSrc} alt={company.name} className={`mx-auto mb-1 ${isThermal ? 'max-h-12 max-w-[200px]' : 'max-h-16 max-w-[280px]'} object-contain`} />
|
||||
) : (
|
||||
<div className={`font-bold ${isThermal ? 'text-sm' : 'text-lg'}`}>{company.name}</div>
|
||||
)}
|
||||
{location.name !== company.name && (
|
||||
<div className="text-gray-600">{location.name}</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user