fix teacher, parent and admin pages

This commit is contained in:
root
2026-04-25 00:00:10 -04:00
parent 7fe34dde0d
commit 3e77fc92c7
275 changed files with 46412 additions and 3325 deletions
@@ -0,0 +1,246 @@
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>
)
}