184 lines
5.6 KiB
JavaScript
184 lines
5.6 KiB
JavaScript
import { useEffect, useState } from 'react'
|
|
import { useNavigate } from 'react-router-dom'
|
|
import { Button, Field, Icon, Modal, SelectInput } from '../../components/ui'
|
|
import ActivityFields from '../../components/activity/ActivityFields'
|
|
import {
|
|
ASSET_FIELDS,
|
|
ASSET_INIT_ACTIVITY_ID,
|
|
createAsset,
|
|
fetchAssignableEmployees,
|
|
uploadAssetFile,
|
|
} from '../../api/assets'
|
|
import { emptyValues, validateActivity } from '../../api/activityForm'
|
|
import { useAuth } from '../../store/AuthContext'
|
|
|
|
// Asset Brand and Asset Location come first, as on the Edit form; the rest
|
|
// keep the builder's order.
|
|
const FIRST_FIELDS = ['field_59', 'field_61']
|
|
const FORM_FIELDS = [
|
|
...FIRST_FIELDS.map((id) => ASSET_FIELDS.find((f) => f.id === id)),
|
|
...ASSET_FIELDS.filter((f) => !FIRST_FIELDS.includes(f.id)),
|
|
]
|
|
|
|
// Asset Owner sits after Condition, as in the builder.
|
|
const OWNER_AFTER = FORM_FIELDS.findIndex((f) => f.id === 'field_47') + 1
|
|
const FIELDS_BEFORE_OWNER = FORM_FIELDS.slice(0, OWNER_AFTER)
|
|
const FIELDS_AFTER_OWNER = FORM_FIELDS.slice(OWNER_AFTER)
|
|
|
|
const upload = (fieldId, file) => uploadAssetFile(ASSET_INIT_ACTIVITY_ID, fieldId, file)
|
|
|
|
/**
|
|
* "Add Asset" dialog. Fields are rendered straight from the Zino builder
|
|
* definition in api/assets.js, so the form always matches the activity.
|
|
* "Asset Owner" is a lookup, so it is picked from a list of active employees.
|
|
*/
|
|
export default function AddAssetModal({ open, onClose, onCreated }) {
|
|
const navigate = useNavigate()
|
|
const { logout } = useAuth()
|
|
|
|
const [values, setValues] = useState(() => emptyValues(ASSET_FIELDS))
|
|
const [employees, setEmployees] = useState([])
|
|
const [loadingEmployees, setLoadingEmployees] = useState(false)
|
|
const [ownerId, setOwnerId] = useState('')
|
|
const [errors, setErrors] = useState({})
|
|
const [serverError, setServerError] = useState('')
|
|
const [submitting, setSubmitting] = useState(false)
|
|
|
|
// A fresh employee list each time the dialog opens.
|
|
useEffect(() => {
|
|
if (!open) return undefined
|
|
let cancelled = false
|
|
setLoadingEmployees(true)
|
|
fetchAssignableEmployees()
|
|
.then((list) => !cancelled && setEmployees(list))
|
|
.catch((err) => {
|
|
if (cancelled) return
|
|
if (err?.status === 401) {
|
|
logout()
|
|
navigate('/login', { replace: true })
|
|
return
|
|
}
|
|
setServerError(err?.message || 'Could not load the employee list.')
|
|
})
|
|
.finally(() => !cancelled && setLoadingEmployees(false))
|
|
return () => {
|
|
cancelled = true
|
|
}
|
|
}, [open, logout, navigate])
|
|
|
|
function reset() {
|
|
setValues(emptyValues(ASSET_FIELDS))
|
|
setOwnerId('')
|
|
setErrors({})
|
|
setServerError('')
|
|
setSubmitting(false)
|
|
}
|
|
|
|
function close() {
|
|
if (submitting) return
|
|
reset()
|
|
onClose?.()
|
|
}
|
|
|
|
function handleChange(id, value) {
|
|
setValues((v) => ({ ...v, [id]: value }))
|
|
if (errors[id]) setErrors((er) => ({ ...er, [id]: undefined }))
|
|
if (serverError) setServerError('')
|
|
}
|
|
|
|
async function handleSubmit(e) {
|
|
e.preventDefault()
|
|
const nextErrors = validateActivity(ASSET_FIELDS, values)
|
|
setErrors(nextErrors)
|
|
if (Object.keys(nextErrors).length) return
|
|
|
|
setSubmitting(true)
|
|
setServerError('')
|
|
try {
|
|
const owner = employees.find((emp) => String(emp.id) === ownerId) ?? null
|
|
const response = await createAsset(values, owner)
|
|
onCreated?.(values, response)
|
|
reset()
|
|
onClose?.()
|
|
} catch (err) {
|
|
if (err?.status === 401) {
|
|
logout()
|
|
navigate('/login', { replace: true })
|
|
return
|
|
}
|
|
setServerError(err?.message || 'Could not create the asset. Please try again.')
|
|
setSubmitting(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Modal
|
|
open={open}
|
|
onClose={close}
|
|
title="Add Asset"
|
|
subtitle="Starts the asset onboarding workflow."
|
|
size="lg"
|
|
footer={
|
|
<>
|
|
<Button onClick={close} disabled={submitting}>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
type="submit"
|
|
form="add-asset-form"
|
|
variant="primary"
|
|
icon={submitting ? undefined : 'plus'}
|
|
disabled={submitting}
|
|
>
|
|
{submitting ? 'Creating…' : 'Create Asset'}
|
|
</Button>
|
|
</>
|
|
}
|
|
>
|
|
{serverError && (
|
|
<div className="auth__alert" role="alert">
|
|
<Icon name="alert" size={15} />
|
|
<span>{serverError}</span>
|
|
</div>
|
|
)}
|
|
|
|
<form id="add-asset-form" className="form__grid" onSubmit={handleSubmit} noValidate>
|
|
<ActivityFields
|
|
fields={FIELDS_BEFORE_OWNER}
|
|
values={values}
|
|
errors={errors}
|
|
onChange={handleChange}
|
|
disabled={submitting}
|
|
upload={upload}
|
|
/>
|
|
<Field label="Asset Owner">
|
|
{(id) => (
|
|
<SelectInput
|
|
id={id}
|
|
value={ownerId}
|
|
onChange={(e) => {
|
|
setOwnerId(e.target.value)
|
|
if (serverError) setServerError('')
|
|
}}
|
|
options={employees.map((emp) => ({
|
|
value: String(emp.id),
|
|
label: emp.code ? `${emp.name} (${emp.code})` : emp.name,
|
|
}))}
|
|
placeholder={loadingEmployees ? 'Loading employees…' : 'Select employee'}
|
|
disabled={submitting || loadingEmployees}
|
|
/>
|
|
)}
|
|
</Field>
|
|
<ActivityFields
|
|
fields={FIELDS_AFTER_OWNER}
|
|
values={values}
|
|
errors={errors}
|
|
onChange={handleChange}
|
|
disabled={submitting}
|
|
upload={upload}
|
|
/>
|
|
</form>
|
|
</Modal>
|
|
)
|
|
}
|