Assets - Read assets from the asset table record view (de18263e-…), keyed by _pk_value; rows open by assetRowId and the list sorts newest first. - Update INIT fields to the builder: Asset Details (was Category), Asset State/Priority/Classification options, warranty fields, Brand, ID, Location, and the images field (field_53); Asset Owner is field_51. - Upload photos and an Invoice document before the activity runs, and send the returned file objects. The Invoice field id is a placeholder until the field exists in the builder. - Show every Add Asset field as a list and details column, with option labels, and group related columns into rich cells. - Use the INIT activity's detail view template (a573754f-…). - Drop Assigned by, Condition and Type from the reassign, return and lost forms; add Lost manager name options. Employees - Department is a select from DEPARTMENTS; more employee types; status removed from the forms; rich columns in the employee list. Shared - apiFetch sends FormData bodies as multipart. - Default workflow host is dev-apps.getzino.in. - displayLabel() for readable field labels; Raw response panels removed. - useRecordView accepts an initial sort. - Temporary dev proxy in vite.config.js for rv-screens lookups. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
138 lines
4.3 KiB
JavaScript
138 lines
4.3 KiB
JavaScript
import { useState } from 'react'
|
|
import { useNavigate } from 'react-router-dom'
|
|
import { Button, Card, Icon, PageHeader, SelectInput } from '../../components/ui'
|
|
import RecordView from '../../components/activity/RecordView'
|
|
import useRecordView from '../../hooks/useRecordView'
|
|
import { RECORD_VIEWS } from '../../api/recordView'
|
|
import {
|
|
EMPLOYEE_RICH_COLUMNS,
|
|
EMPLOYMENT_FILTERS,
|
|
fetchEmployeeListPage,
|
|
resolveEmployeeInstanceId,
|
|
} from '../../api/employees'
|
|
import { withRichColumns } from '../../api/richColumns'
|
|
import { useAuth } from '../../store/AuthContext'
|
|
import AddEmployeeModal from './AddEmployeeModal'
|
|
|
|
const VIEW = RECORD_VIEWS.employees
|
|
|
|
export default function EmployeeList() {
|
|
const navigate = useNavigate()
|
|
const { logout } = useAuth()
|
|
// The table misses Update fields Zino doesn't map and knows nothing of the
|
|
// workflow state, so this fetcher overlays both and filters Active/Inactive.
|
|
// Related columns (name and code, email and phone…) are grouped into one.
|
|
const view = useRecordView(VIEW.templateUid, {
|
|
fetcher: fetchEmployeeListPage,
|
|
filter: 'active',
|
|
transform: (res) => withRichColumns(res, EMPLOYEE_RICH_COLUMNS),
|
|
})
|
|
const [adding, setAdding] = useState(false)
|
|
const [opening, setOpening] = useState(false)
|
|
const [notice, setNotice] = useState(null)
|
|
|
|
/** `values` is keyed by Zino field id — see EMPLOYEE_FIELDS in api/employees.js. */
|
|
function handleCreated(values) {
|
|
const name = [values.field_1, values.field_34].filter(Boolean).join(' ').trim()
|
|
setNotice({
|
|
tone: 'success',
|
|
text: `${name || 'Employee'} was created and onboarding has started.`,
|
|
})
|
|
// Records live in the workflow, so re-read the view rather than guessing.
|
|
view.reload()
|
|
}
|
|
|
|
/**
|
|
* Open the detail view for a row. The detail view needs the workflow
|
|
* instance_id: use the row's own when it has one, otherwise look it up.
|
|
*/
|
|
async function openDetails(row) {
|
|
if (opening) return
|
|
setOpening(true)
|
|
setNotice(null)
|
|
try {
|
|
const instanceId = await resolveEmployeeInstanceId(row)
|
|
if (instanceId == null) {
|
|
setNotice({
|
|
tone: 'danger',
|
|
text: 'Could not find this employee in the workflow, so its details cannot be shown.',
|
|
})
|
|
return
|
|
}
|
|
navigate(`/employees/${encodeURIComponent(instanceId)}`)
|
|
} catch (err) {
|
|
if (err?.status === 401) {
|
|
logout()
|
|
navigate('/login', { replace: true })
|
|
return
|
|
}
|
|
setNotice({ tone: 'danger', text: err?.message || 'Could not open this employee.' })
|
|
} finally {
|
|
setOpening(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<PageHeader
|
|
title="Employees"
|
|
subtitle="All staff on record, read live from the employee workflow."
|
|
actions={
|
|
<Button variant="primary" icon="plus" onClick={() => setAdding(true)}>
|
|
Add Employee
|
|
</Button>
|
|
}
|
|
/>
|
|
|
|
{opening && (
|
|
<div className="notice notice--warning" role="status">
|
|
<Icon name="search" size={15} />
|
|
<span>Opening employee…</span>
|
|
</div>
|
|
)}
|
|
|
|
{notice && (
|
|
<div className={`notice notice--${notice.tone}`} role="status">
|
|
<Icon name={notice.tone === 'success' ? 'check' : 'alert'} size={15} />
|
|
<span>{notice.text}</span>
|
|
<button
|
|
type="button"
|
|
className="notice__close"
|
|
onClick={() => setNotice(null)}
|
|
aria-label="Dismiss"
|
|
>
|
|
<Icon name="close" size={14} />
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
<Card flush>
|
|
<RecordView
|
|
{...view}
|
|
rowKey={VIEW.rowKey}
|
|
emptyText={
|
|
view.query.filter === 'inactive' ? 'No inactive employees.' : 'No employees yet.'
|
|
}
|
|
onRowClick={openDetails}
|
|
toolbar={
|
|
<div className="filter">
|
|
<SelectInput
|
|
value={view.query.filter}
|
|
onChange={(e) => view.setFilter(e.target.value)}
|
|
options={EMPLOYMENT_FILTERS}
|
|
aria-label="Show employees"
|
|
/>
|
|
</div>
|
|
}
|
|
/>
|
|
</Card>
|
|
|
|
<AddEmployeeModal
|
|
open={adding}
|
|
onClose={() => setAdding(false)}
|
|
onCreated={handleCreated}
|
|
/>
|
|
</>
|
|
)
|
|
}
|