vat_wms/resources/js/Components/BatchInfoWindow.tsx

210 lines
8.5 KiB
TypeScript

// components/BatchInfoWindow.tsx
import React, { useEffect, useMemo, useState } from 'react'
import {
MaterialReactTable,
type MRT_ColumnDef,
type MRT_PaginationState,
type MRT_SortingState,
} from 'material-react-table'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faQrcode, faListOl } from '@fortawesome/free-solid-svg-icons'
import { NextRouter } from 'next/router'
import { StockBatch, StockEntry, Supplier } from '@/types'
import axios from 'axios'
import { toast } from 'react-hot-toast'
import { size } from 'lodash'
import {downloadBatchBarcode} from "@/functions/functions"
interface Status {
id: number
name: string
}
interface BatchInfoWindowProps {
selectedBatch: StockBatch | null
formatDate?: (value: string, withTime?: boolean) => string
router: NextRouter
route: (name: string, params: Record<string, any>) => string
/** We now allow `entries` to be null, but internally we coalesce to [] */
entries: StockEntry[] | null
/** Any other props you had (for pagination / sorting, etc.) */
entryColumns?: MRT_ColumnDef<StockEntry>[]
entriesCount?: number
entriesLoading?: boolean
entriesPagination?: MRT_PaginationState
entriesSorting?: MRT_SortingState
entriesFilter?: string
setEntriesPagination?: (updater: MRT_PaginationState) => void
setEntriesSorting?: (sorter: MRT_SortingState) => void
setEntriesFilter?: (filter: string) => void
openEntry?: (entry: StockEntry) => void
recountEnabled?: boolean
}
const defaultFormatter = (value: string, withTime = true) => {
const d = new Date(value)
const dd = String(d.getDate()).padStart(2, '0')
const mm = String(d.getMonth() + 1).padStart(2, '0')
const yyyy = d.getFullYear()
if (!withTime) return `${dd}-${mm}-${yyyy}`
const hh = String(d.getHours()).padStart(2, '0')
const mi = String(d.getMinutes()).padStart(2, '0')
return `${dd}-${mm}-${yyyy} ${hh}:${mi}`
}
const statusIds = [2, 6, 7, 8]
export default function BatchInfoWindow({
selectedBatch,
formatDate = defaultFormatter,
router,
route,
openEntry,
recountEnabled
}: BatchInfoWindowProps) {
const [statuses, setStatuses] = useState<Status[]>([])
/** Always keep an array here (never null). */
/** Fetch statuses once */
useEffect(() => {
console.log(selectedBatch);
axios
.get('/api/stockStatusList')
.then((res) => setStatuses(res.data.statuses))
.catch(() => toast.error('Failed to load status list'))
}, [])
/** Example helper to calculate ratios */
function calculateStatusRatio(entries: StockEntry[], statusId: number) {
const total = entries.length
const count = entries.filter((e) =>
e.status_history?.some((h: any) => h.stock_entries_status_id === statusId)
).length
const ratio = total > 0 ? count / total : 0
return { count, total, ratio }
}
const entryColumnsMemo = useMemo<MRT_ColumnDef<StockEntry>[]>(
() => [
{ accessorKey: 'id', header: 'ID', size: 80 },
{ accessorKey: 'physical_item.name', header: 'Physical Item', size: 200 },
{ accessorKey: 'supplier.name', header: 'Supplier', size: 150 },
{
accessorKey: 'price',
header: 'Price',
size: 100,
Cell: ({ cell }) => cell.getValue<number>()?.toFixed(2) || '-',
},
// …add the rest of your columns here…
],
[]
)
const hasNonCountedStatus = selectedBatch?.stock_entries?.some((stockEntry) => {
const nullSectionStatuses =
stockEntry.status_history?.filter((h: any) => h.section_id === null) ?? []
if (nullSectionStatuses.length === 0) return true
const latest = nullSectionStatuses.reduce((prev, curr) =>
new Date(prev.created_at) > new Date(curr.created_at) ? prev : curr
)
return latest.stock_entries_status_id !== 2
})
return (
<div className="flex sm:flex-row flex-col space-y-4 sm:space-y-0 sm:space-x-4">
{/* Left: Batch Details & Ratios */}
<div className="sm:w-1/3 flex flex-col justify-between">
<div>
<h3 className="font-bold text-lg mb-2">Batch Details</h3>
{selectedBatch ? (
<ul className="list-disc list-inside">
<li>
<strong>ID:</strong> {selectedBatch.id}
</li>
<li>
<strong>Supplier:</strong> {selectedBatch.supplier.name}
</li>
<li>
<strong>Tracking #:</strong> {selectedBatch.tracking_number}
</li>
<li>
<strong>Arrival:</strong>{' '}
{formatDate(selectedBatch.arrival_date, false)}
</li>
</ul>
) : (
<p className="text-sm text-gray-500">No batch selected.</p>
)}
{selectedBatch && (
<div style={{ marginTop: 24 }}>
<h3 className="font-semibold text-md mb-2">Status Ratios</h3>
{statusIds.map((id) => {
const { count, total, ratio } = calculateStatusRatio(
selectedBatch.stock_entries,
id
)
const statusData = statuses.find((s) => s.id === id)
return (
<p key={id} className="text-sm">
{statusData?.name}: {count} / {total} (
{(ratio * 100).toFixed(1)}%)
</p>
)
})}
</div>
)}
</div>
<div className="mt-4 flex space-x-2">
<div className="tooltip" data-tip="Batch QR kód">
<button className="btn bg-[#666666] text-[19px]" onClick={() => downloadBatchBarcode(selectedBatch?.id)}>
<FontAwesomeIcon icon={faQrcode} />
</button>
</div>
{hasNonCountedStatus && selectedBatch && recountEnabled && (
<div className="tooltip" data-tip="Přepočítat">
<button
className="btn btn-warning text-[19px]"
onClick={() =>
router.get(
route('batchCounting', { selectedBatch: selectedBatch.id })
)
}
>
<FontAwesomeIcon icon={faListOl} />
</button>
</div>
)}
</div>
</div>
{/* Right: Stock Entries Table (never pass null!) */}
<div className="sm:w-2/3 flex flex-col sm:pl-6">
<h3 className="font-bold text-lg mb-2">Stock Entries</h3>
<div className="flex-1 overflow-auto">
<MaterialReactTable
columns={entryColumnsMemo}
data={selectedBatch?.stock_entries} // guaranteed array, never null
manualPagination
manualSorting
enableGlobalFilter
initialState={{ pagination: { pageSize: 100 } }}
muiTableBodyRowProps={({ row }) => ({
onClick: () => openEntry?.(row.original),
style: { cursor: 'pointer' },
})}
/>
</div>
</div>
</div>
)
}