vat_wms/resources/js/Components/modals/EditStockSections.tsx
2025-07-09 15:35:23 +02:00

348 lines
14 KiB
TypeScript

// components/modals/EditStockSections.tsx
import React from 'react'
import axios from 'axios'
import { toast } from 'react-hot-toast'
import { StockPosition, StockSection } from "@/types"
interface Props {
onClose: () => void
selectedPosition: StockPosition
}
interface EditableSection {
// Mirror of StockSection, with local-only flags
section_id?: number
section_symbol: string
section_name: string
capacity: number
retrievable: boolean
markedForDeletion: boolean
}
const EditStockSections: React.FC<Props> = ({ onClose, selectedPosition }) => {
// Local state for position capacity and editable sections
const [positionCapacity, setPositionCapacity] = React.useState<number>(selectedPosition.capacity)
const [sections, setSections] = React.useState<EditableSection[]>([])
const [loading, setLoading] = React.useState<boolean>(false)
const [validationError, setValidationError] = React.useState<string>("")
// Initialize sections state whenever the selected position changes
React.useEffect(() => {
const initialSections: EditableSection[] = selectedPosition.sections.map((sec) => ({
section_id: sec.section_id,
section_symbol: sec.section_symbol,
section_name: sec.section_name,
capacity: sec.capacity,
retrievable: sec.retrievable,
markedForDeletion: false,
}))
setSections(initialSections)
setPositionCapacity(selectedPosition.capacity)
setValidationError("")
}, [selectedPosition])
// Handler: change position capacity
const handlePositionCapacityChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const val = parseInt(e.target.value, 10)
if (!isNaN(val) && val >= 0) {
setPositionCapacity(val)
// Clear any existing validation error to re-validate
setValidationError("")
}
}
// Handler: change a section's field (by index)
const handleSectionChange = (
index: number,
field: keyof Omit<EditableSection, "section_id" | "markedForDeletion">,
value: string | boolean
) => {
setSections((prev) => {
const updated = [...prev]
if (field === "capacity") {
const num = parseInt(value as string, 10)
updated[index].capacity = isNaN(num) ? 0 : num
} else if (field === "retrievable") {
updated[index].retrievable = value as boolean
} else {
updated[index][field] = value as string
}
return updated
})
setValidationError("")
}
// Handler: add a brand-new empty section
const handleAddSection = () => {
setSections((prev) => [
...prev,
{
section_symbol: `${prev.length + 1}`,
section_name: `Section ${prev.length + 1}`,
capacity: 0,
retrievable: true,
markedForDeletion: false,
},
])
setValidationError("")
}
// Handler: mark a section for deletion (or directly remove if never saved)
const handleRemoveSection = (index: number) => {
setSections((prev) => {
const updated = [...prev]
if (updated[index].section_id) {
// If it exists on the server, mark for deletion
updated[index].markedForDeletion = true
} else {
// If it's a new unsaved section, just drop it
updated.splice(index, 1)
}
return updated
})
setValidationError("")
}
// Compute total capacity of non-deleted sections
const totalSectionsCapacity = sections
.filter((s) => !s.markedForDeletion)
.reduce((sum, s) => sum + s.capacity, 0)
// Validation: total section capacity must not exceed position capacity
React.useEffect(() => {
if (totalSectionsCapacity > positionCapacity) {
setValidationError(
`Total section capacity (${totalSectionsCapacity}) exceeds position capacity (${positionCapacity}).`
)
} else {
setValidationError("")
}
}, [totalSectionsCapacity, positionCapacity])
// Handler: save all changes (position + sections)
const handleSave = async () => {
// Prevent saving if there's a validation error
if (validationError) {
toast.error(validationError)
return
}
setLoading(true)
const toastId = toast.loading("Saving changes…")
try {
// 1) Update the StockPosition's capacity if it has changed
if (positionCapacity !== selectedPosition.capacity) {
await axios.put(
`/api/stockPositions/${selectedPosition.position_id}`,
{
capacity: positionCapacity,
},
{ withCredentials: true }
)
}
// 2) Process each section: create, update, or delete
for (const sec of sections) {
if (sec.section_id && sec.markedForDeletion) {
// DELETE existing section
await axios.delete(`/api/stockSections/${sec.section_id}`, {
withCredentials: true,
})
} else if (sec.section_id) {
// UPDATE existing section
await axios.put(
`/api/stockSections/${sec.section_id}`,
{
section_name: sec.section_name,
capacity: sec.capacity,
retrievable: sec.retrievable,
},
{ withCredentials: true }
)
} else if (!sec.section_id && !sec.markedForDeletion) {
// CREATE new section
await axios.post(
`/api/stockSections`,
{
position_id: selectedPosition.position_id,
section_symbol: sec.section_symbol,
section_name: sec.section_name,
capacity: sec.capacity,
retrievable: sec.retrievable,
},
{ withCredentials: true }
)
}
// If a new section was added then immediately marked for deletion before save,
// we do nothing (skip).
}
toast.dismiss(toastId)
toast.success("Position and sections updated successfully.")
onClose()
} catch (err: any) {
toast.dismiss(toastId)
if (err.response && err.response.data && err.response.data.message) {
toast.error(`Error: ${err.response.data.message}`)
} else {
toast.error("Network or server error. Please try again.")
}
} finally {
setLoading(false)
}
}
return (
<div className="space-y-4">
<h3 className="font-bold text-lg"> Edit<span className="italic opacity-75 border-l-2 pl-2 ml-2">Position {selectedPosition.storage_address}</span> </h3>
{/* Position Capacity Input */}
<div className="form-control w-full">
<label className="label">
<span className="label-text">Position Capacity</span>
</label>
<input
type="number"
value={positionCapacity || ""}
onChange={handlePositionCapacityChange}
className="input input-bordered w-full"
min="0"
required
/>
</div>
{/* Sections List */}
<div className="space-y-2">
<div className="flex justify-between items-center">
<h4 className="font-semibold">Sections</h4>
<button
type="button"
className="btn btn-sm btn-outline"
onClick={handleAddSection}
>
+ Add Section
</button>
</div>
{sections.map((sec, idx) => (
<div
key={sec.section_id ?? `new-${idx}`}
className={`border rounded p-3 ${
sec.markedForDeletion ? "opacity-50 italic" : ""
}`}
>
<div className="flex justify-between items-center mb-2">
<span className="font-medium">
{sec.section_id
? `Section ID: ${sec.section_id}`
: `New Section`}
</span>
<button
type="button"
className="btn btn-error btn-xs"
onClick={() => handleRemoveSection(idx)}
>
{sec.section_id ? "Delete" : "Remove"}
</button>
</div>
{!sec.markedForDeletion && (
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{/* Section Name */}
<div className="form-control">
<label className="label">
<span className="label-text">Name</span>
</label>
<input
type="text"
value={sec.section_name}
onChange={(e) =>
handleSectionChange(idx, "section_name", e.target.value)
}
className="input input-bordered w-full"
required
/>
</div>
{/* Section Capacity */}
<div className="form-control">
<label className="label">
<span className="label-text">Capacity</span>
</label>
<input
type="number"
value={sec.capacity}
onChange={(e) =>
handleSectionChange(idx, "capacity", e.target.value)
}
className="input input-bordered w-full"
min="0"
required
/>
</div>
{/* Retrievable Checkbox */}
<div className="form-control flex items-center pt-6">
<label className="label cursor-pointer">
<input
type="checkbox"
checked={sec.retrievable}
onChange={(e) =>
handleSectionChange(idx, "retrievable", e.target.checked)
}
className="checkbox checkbox-primary mr-2"
/>
<span className="label-text">Retrievable</span>
</label>
</div>
</div>
)}
{sec.markedForDeletion && (
<div className="text-sm text-red-600">
This section will be deleted when you save.
</div>
)}
</div>
))}
{sections.length === 0 && (
<div className="text-gray-500 italic">
No sections defined for this position.
</div>
)}
</div>
{/* Validation Error */}
{validationError && (
<div className="text-red-600 font-medium">{validationError}</div>
)}
<div className="text-sm text-gray-600">
Total of section capacities: <strong>{totalSectionsCapacity}</strong>
</div>
{/* Action Buttons */}
<div className="flex space-x-2 pt-4">
<button
type="button"
className="btn btn-primary"
onClick={handleSave}
disabled={!!validationError || loading}
>
{loading ? "Saving…" : "Save Changes"}
</button>
<button
type="button"
className="btn btn-outline"
onClick={onClose}
disabled={loading}
>
Cancel
</button>
</div>
</div>
)
}
export default EditStockSections