211 lines
8.6 KiB
TypeScript
211 lines
8.6 KiB
TypeScript
import React from 'react'
|
|
import axios from 'axios'
|
|
import { toast } from 'react-hot-toast'
|
|
import { StockPosition, StockEntry } from '@/types'
|
|
|
|
interface Props {
|
|
onClose: () => void
|
|
selectedPosition: StockPosition
|
|
}
|
|
|
|
const ChangeCountModal: React.FC<Props> = ({ onClose, selectedPosition }) => {
|
|
const [selectedEntry, setSelectedEntry] = React.useState<StockEntry | null>(null)
|
|
const [isDropdownOpen, setIsDropdownOpen] = React.useState(false)
|
|
const [newCount, setNewCount] = React.useState<number | null>(null)
|
|
const [loading, setLoading] = React.useState(false)
|
|
|
|
|
|
const handleChangeCount = async () => {
|
|
|
|
setLoading(true)
|
|
const toastId = toast.loading('Changing count…')
|
|
|
|
try {
|
|
const { data } = await axios.post(
|
|
'/api/pdaView/changeCount',
|
|
{
|
|
entry_id: selectedEntry.id,
|
|
section_id: selectedEntry.pivot.section_id,
|
|
count: selectedEntry.pivot.count,
|
|
new_count: newCount,
|
|
},
|
|
{ withCredentials: true }
|
|
)
|
|
|
|
if (!data.success) {
|
|
toast.dismiss(toastId)
|
|
switch (data.error) {
|
|
case 'validation_failed':
|
|
toast.error('Validation failed. Check inputs.')
|
|
break
|
|
case 'not_found':
|
|
toast.error('Entry or section not found.')
|
|
break
|
|
case 'section_occupied':
|
|
toast.error('Target section is already occupied.')
|
|
break
|
|
case 'insufficient_capacity':
|
|
toast.error('Not enough capacity in target section.')
|
|
break
|
|
default:
|
|
toast.error(data.message ?? 'Server error during count change.')
|
|
break
|
|
}
|
|
setLoading(false)
|
|
return
|
|
}
|
|
|
|
toast.dismiss(toastId)
|
|
toast.success('Count changed on position.')
|
|
resetAndClose()
|
|
} catch (err: any) {
|
|
toast.dismiss()
|
|
if (err.response && err.response.data) {
|
|
const payload = err.response.data
|
|
if (payload.error === 'validation_failed') {
|
|
toast.error('Validation failed. Check inputs.')
|
|
} else if (payload.error === 'not_found') {
|
|
toast.error('Entry or section not found.')
|
|
} else if (payload.error === 'section_occupied') {
|
|
toast.error('Target section is already occupied.')
|
|
} else if (payload.error === 'insufficient_capacity') {
|
|
toast.error('Not enough capacity in target section.')
|
|
} else {
|
|
toast.error(payload.message || 'Unknown error occurred.')
|
|
}
|
|
} else {
|
|
toast.error('Network error. Please try again.')
|
|
}
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
const resetAndClose = () => {
|
|
setSelectedEntry(null)
|
|
setNewCount(null)
|
|
setLoading(false)
|
|
setIsDropdownOpen(false)
|
|
onClose()
|
|
}
|
|
|
|
// Build flat list of entries grouped by section
|
|
const entriesList = React.useMemo<StockEntry[]>(() => {
|
|
if (!selectedPosition.sections) return []
|
|
return selectedPosition.sections.flatMap((section) =>
|
|
section.entries.map((entry) => ({ ...entry, pivot: entry.pivot, _section: section }))
|
|
)
|
|
}, [selectedPosition])
|
|
console.log(entriesList);
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<h3 className="font-bold text-lg">Change item count</h3>
|
|
|
|
{/* Entry Dropdown */}
|
|
<div className="form-control w-full">
|
|
<label className="label">
|
|
<span className="label-text">Select Item</span>
|
|
</label>
|
|
<div className="dropdown w-full">
|
|
<button
|
|
type="button"
|
|
className="btn w-full justify-between"
|
|
onClick={() => setIsDropdownOpen((prev) => !prev)}
|
|
>
|
|
{selectedEntry ? (
|
|
<div className="flex items-center space-x-2">
|
|
<img
|
|
src={selectedEntry?.physical_item?.image_url}
|
|
alt={selectedEntry?.physical_item?.name}
|
|
className="w-6 h-6 rounded-full"
|
|
/>
|
|
<span>
|
|
{selectedEntry?.physical_item?.name} (Count: {selectedEntry?.pivot.count}) in Sect.{' '}
|
|
{entriesList.find(e => e.id === selectedEntry.id)._section.section_symbol}
|
|
</span>
|
|
</div>
|
|
) : (
|
|
'Select item...'
|
|
)}
|
|
<svg
|
|
className="fill-current w-4 h-4"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
viewBox="0 0 20 20"
|
|
>
|
|
<path d="M5.516 7.548a.75.75 0 0 1 1.06 0L10 10.972l3.424-3.424a.75.75 0 1 1 1.06 1.06l-4 4a.75.75 0 0 1-1.06 0l-4-4a.75.75 0 0 1 0-1.06z"/>
|
|
</svg>
|
|
</button>
|
|
<ul
|
|
tabIndex={0}
|
|
className={`dropdown-content menu p-2 shadow bg-base-100 rounded-box w-full mt-1 ${
|
|
isDropdownOpen ? 'block' : 'hidden'
|
|
}`}
|
|
>
|
|
{entriesList.map((entry) => (
|
|
<li key={`${entry.id}-${entry.pivot.section_id}`}>
|
|
<button
|
|
type="button"
|
|
className="flex items-center px-2 py-1 hover:bg-gray-100 rounded"
|
|
onClick={() => {
|
|
setSelectedEntry(entry)
|
|
setIsDropdownOpen(false)
|
|
}}
|
|
>
|
|
<img
|
|
src={entry.physical_item?.image_url}
|
|
alt={entry.physical_item?.name}
|
|
className="w-6 h-6 rounded-full mr-2"
|
|
/>
|
|
<span>
|
|
{entry.physical_item?.name} ({entry.pivot?.count} ks) — Section {entry._section?.section_symbol}
|
|
</span>
|
|
</button>
|
|
</li>
|
|
))}
|
|
{entriesList.length === 0 && (
|
|
<li>
|
|
<span className="px-2 py-1 text-gray-500">No items in this position</span>
|
|
</li>
|
|
)}
|
|
</ul>
|
|
</div>
|
|
{selectedEntry ? (
|
|
<label className="input">
|
|
<input type="number" className="grow" placeholder="123" onChange={(e) => setNewCount(parseInt(e.target.value))} />
|
|
<kbd className="kbd kbd-sm">123</kbd>
|
|
</label>
|
|
): ""}
|
|
</div>
|
|
|
|
{/* Simulate Scan Button */}
|
|
<div className="flex space-x-2">
|
|
<button
|
|
type="button"
|
|
className="btn btn-primary"
|
|
onClick={handleChangeCount}
|
|
disabled={!selectedEntry || loading}
|
|
>
|
|
Confirm
|
|
</button>
|
|
</div>
|
|
|
|
{/* Waiting for scan indicator */}
|
|
{selectedEntry && newCount === null && !loading && (
|
|
<div className="flex items-center space-x-2 mt-2">
|
|
<span>Waiting for section scan...</span>
|
|
<span className="loading loading-spinner text-primary"></span>
|
|
</div>
|
|
)}
|
|
|
|
{/* Cancel Button */}
|
|
<div className="modal-action flex justify-end pt-2">
|
|
<button type="button" className="btn" onClick={resetAndClose}>
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default ChangeCountModal
|