Files
alrahma_web_client/src/pages/inventory/InventoryBookFormPage.tsx
T
2026-04-25 00:00:10 -04:00

247 lines
9.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { type FormEvent, useEffect, useState } from 'react'
import { Link, useLocation, useNavigate, useParams, useSearchParams } from 'react-router-dom'
import { ApiHttpError } from '../../api/http'
import {
fetchInventoryCreateForm,
fetchInventoryItem,
postInventoryItem,
putInventoryItem,
} from '../../api/inventory'
import { catLabelBook, gradeLabel, parseClassesSelection } from './inventoryHelpers'
import { INVENTORY_BOOK_BASE } from './inventoryPaths'
/** CI `inventory/book/form.php` */
export function InventoryBookFormPage() {
const { itemId } = useParams()
const { pathname } = useLocation()
const navigate = useNavigate()
const [searchParams] = useSearchParams()
const isCreate = pathname.endsWith('/create')
const [categories, setCategories] = useState<Record<string, unknown>[]>([])
const [name, setName] = useState('')
const [categoryId, setCategoryId] = useState('')
const [classes, setClasses] = useState<number[]>([])
const [author, setAuthor] = useState('')
const [isbn, setIsbn] = useState('')
const [edition, setEdition] = useState('')
const [quantity, setQuantity] = useState('0')
const [unit, setUnit] = useState('')
const [sku, setSku] = useState('')
const [description, setDescription] = useState('')
const [loading, setLoading] = useState(true)
const [saving, setSaving] = useState(false)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
let c = false
setLoading(true)
const run = isCreate
? fetchInventoryCreateForm('book', searchParams)
: fetchInventoryItem(itemId ?? '')
run
.then((data) => {
if (c) return
if (data && typeof data === 'object') {
const o = data as Record<string, unknown>
const cats = Array.isArray(o.categories)
? (o.categories as Record<string, unknown>[])
: []
setCategories(cats)
const item = (o.item ?? o) as Record<string, unknown>
if (!isCreate && item && typeof item === 'object') {
setName(String(item.name ?? ''))
setCategoryId(item.category_id != null ? String(item.category_id) : '')
setClasses(parseClassesSelection(item))
setAuthor(String(item.author ?? ''))
setIsbn(String(item.isbn ?? ''))
setEdition(String(item.edition ?? ''))
setQuantity(String(item.quantity ?? 0))
setUnit(String(item.unit ?? ''))
setSku(String(item.sku ?? ''))
setDescription(String(item.description ?? ''))
}
}
setError(null)
})
.catch((e: unknown) => {
if (!c) setError(e instanceof ApiHttpError ? e.message : 'Unable to load form.')
})
.finally(() => {
if (!c) setLoading(false)
})
return () => {
c = true
}
}, [isCreate, itemId, searchParams])
const selectedCat = categories.find((x) => String(x.id) === categoryId)
const gradeRangeText = gradeLabel(selectedCat ?? null)
function toggleClass(n: number) {
setClasses((prev) =>
prev.includes(n) ? prev.filter((x) => x !== n) : [...prev, n].sort((a, b) => a - b),
)
}
async function onSubmit(e: FormEvent<HTMLFormElement>) {
e.preventDefault()
setSaving(true)
setError(null)
const body: Record<string, unknown> = {
type: 'book',
name,
category_id: categoryId === '' ? null : Number(categoryId),
classes,
author: author || undefined,
isbn: isbn || undefined,
edition: edition || undefined,
quantity: quantity === '' ? 0 : Number(quantity),
unit: unit || undefined,
sku: sku || undefined,
description: description || undefined,
}
try {
if (isCreate) await postInventoryItem(body)
else await putInventoryItem(itemId ?? '', body)
navigate(INVENTORY_BOOK_BASE)
} catch (err: unknown) {
setError(err instanceof ApiHttpError ? err.message : 'Save failed.')
} finally {
setSaving(false)
}
}
return (
<div className="container my-4">
<h3 className="mb-3">{isCreate ? 'Add' : 'Edit'} Book</h3>
{error ? <div className="alert alert-danger">{error}</div> : null}
{loading ? <p className="text-muted">Loading</p> : null}
{!loading ? (
<form onSubmit={(ev) => void onSubmit(ev)}>
<div className="row g-3">
<div className="col-md-6">
<label className="form-label">Title</label>
<input
className="form-control"
required
value={name}
onChange={(e) => setName(e.target.value)}
/>
</div>
<div className="col-md-6">
<label className="form-label">Category</label>
<select
className="form-select"
value={categoryId}
onChange={(e) => setCategoryId(e.target.value)}
>
<option value=""> None </option>
{categories.map((c) => (
<option key={String(c.id)} value={String(c.id)}>
{catLabelBook(c)}
</option>
))}
</select>
<div className="form-text">Grade Range: {gradeRangeText}</div>
</div>
<div className="col-12">
<label className="form-label d-flex align-items-center gap-2 flex-wrap">
Classes (113)
<button
type="button"
className="btn btn-sm btn-outline-secondary"
onClick={() => setClasses(Array.from({ length: 13 }, (_, i) => i + 1))}
>
Select All
</button>
<button type="button" className="btn btn-sm btn-outline-secondary" onClick={() => setClasses([])}>
Clear
</button>
</label>
<div className="row g-2">
{Array.from({ length: 13 }, (_, i) => i + 1).map((n) => (
<div key={n} className="col-6 col-sm-4 col-md-3 col-lg-2">
<div className="form-check">
<input
className="form-check-input"
type="checkbox"
id={`class${n}`}
checked={classes.includes(n)}
onChange={() => toggleClass(n)}
/>
<label className="form-check-label" htmlFor={`class${n}`}>
Class {n}
</label>
</div>
</div>
))}
</div>
<div className="form-text">Choose all class numbers that use this book.</div>
</div>
<div className="col-md-4">
<label className="form-label">Author</label>
<input className="form-control" value={author} onChange={(e) => setAuthor(e.target.value)} />
</div>
<div className="col-md-4">
<label className="form-label">ISBN</label>
<input className="form-control" value={isbn} onChange={(e) => setIsbn(e.target.value)} />
</div>
<div className="col-md-4">
<label className="form-label">Edition</label>
<input className="form-control" value={edition} onChange={(e) => setEdition(e.target.value)} />
</div>
<div className="col-md-2">
<label className="form-label">Quantity</label>
<input
type="number"
min={0}
className="form-control"
value={quantity}
onChange={(e) => setQuantity(e.target.value)}
/>
</div>
<div className="col-md-2">
<label className="form-label">Unit</label>
<input
className="form-control"
placeholder="pcs, set"
value={unit}
onChange={(e) => setUnit(e.target.value)}
/>
</div>
<div className="col-md-4">
<label className="form-label">SKU</label>
<input className="form-control" value={sku} onChange={(e) => setSku(e.target.value)} />
</div>
<div className="col-12">
<label className="form-label">Description / Notes</label>
<textarea
className="form-control"
rows={3}
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
</div>
</div>
<div className="mt-3 d-flex gap-2">
<button type="submit" className="btn btn-primary" disabled={saving}>
{saving ? 'Saving…' : isCreate ? 'Save' : 'Update'}
</button>
<Link className="btn btn-outline-secondary" to={INVENTORY_BOOK_BASE}>
Cancel
</Link>
</div>
</form>
) : null}
</div>
)
}