asset-management/src/api/richColumns.js
vamsi.balaga f845af5125 feat: asset table view, file uploads, rich list columns and label cleanup
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>
2026-10-09 11:32:19 +05:30

60 lines
2.3 KiB
JavaScript

import { normLabel } from './detailView'
/**
* Rich columns: several related record view columns shown as one cell — a
* bold title line and a smaller line under it (e.g. name over employee code).
*
* A group is
* { key, label, avatar?, title: [label…], subtitle: [label | { label, prefix }…] }
* where each label names a view column by its output_label or field_key
* ('first_name', 'Asset Brand'…, compared loosely). Title parts are joined
* with a space, subtitle parts with ' · '; `prefix` goes before a value
* ("Ends 12 Oct 2026"). `avatar` shows the title's initials.
*
* The member columns are replaced by the rich column, placed where its first
* member was. A group none of whose columns the view has is skipped; columns
* no group takes stay as they are. Rows are not changed — the cell reads each
* member's own value.
*/
export function withRichColumns(res, groups) {
const fields = res?.config?.fields ?? []
const matches = (col, label) =>
[col.output_label, col.field_key].some((l) => l && normLabel(l) === normLabel(label))
const taken = new Set()
const take = (spec) => {
const { label, prefix } = typeof spec === 'string' ? { label: spec } : spec
const col = fields.find((c) => !taken.has(c) && matches(c, label))
if (!col) return []
taken.add(col)
return [{ ...col, prefix }]
}
const richAt = new Map()
for (const group of groups) {
const title = group.title.flatMap(take)
const subtitle = (group.subtitle ?? []).flatMap(take)
const members = [...title, ...subtitle]
if (!members.length) continue
// Sorting by the column sorts by its first title field (or first member).
const sortBy = title[0] ?? members[0]
const first = fields.findIndex((c) => members.some((m) => m.field_key === c.field_key))
richAt.set(first, {
type: 'rich',
field_key: `rich_${group.key}`,
output_label: group.label,
data_type: 'rich',
avatar: Boolean(group.avatar),
title,
subtitle,
sort_key: sortBy.field_key,
sortable: sortBy.sortable !== false && sortBy.type !== 'derived',
})
}
const next = fields.flatMap((c, i) => {
if (richAt.has(i)) return [richAt.get(i)]
return taken.has(c) ? [] : [c]
})
return { ...res, config: { ...res?.config, fields: next } }
}