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>
86 lines
2.3 KiB
JavaScript
86 lines
2.3 KiB
JavaScript
/**
|
|
* Thin fetch wrapper for the Zino backend.
|
|
* Base URL comes from VITE_API_BASE_URL (see .env.example); falls back to dev.
|
|
*/
|
|
|
|
/** Auth host (login). */
|
|
export const API_BASE_URL = (
|
|
import.meta.env.VITE_API_BASE_URL || 'https://dev-apps.getzino.in'
|
|
).replace(/\/+$/, '')
|
|
|
|
/** App/workflow host (activities such as "start"). */
|
|
export const APP_API_BASE_URL = (
|
|
import.meta.env.VITE_APP_API_BASE_URL || 'https://dev-apps.getzino.in'
|
|
).replace(/\/+$/, '')
|
|
|
|
export const TOKEN_KEY = 'oam.auth.token'
|
|
export const USER_KEY = 'oam.auth.user'
|
|
|
|
export class ApiError extends Error {
|
|
constructor(message, { status, data } = {}) {
|
|
super(message)
|
|
this.name = 'ApiError'
|
|
this.status = status
|
|
this.data = data
|
|
}
|
|
}
|
|
|
|
export function getStoredToken() {
|
|
try {
|
|
return localStorage.getItem(TOKEN_KEY)
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* JSON request helper. Attaches the bearer token when one is stored,
|
|
* parses JSON responses and throws ApiError on non-2xx.
|
|
* A FormData body is sent as multipart; the browser sets its boundary.
|
|
*/
|
|
export async function apiFetch(path, { method = 'GET', body, headers, auth = true, ...rest } = {}) {
|
|
const url = path.startsWith('http') ? path : `${API_BASE_URL}${path}`
|
|
const token = auth ? getStoredToken() : null
|
|
const isForm = body instanceof FormData
|
|
|
|
let response
|
|
try {
|
|
response = await fetch(url, {
|
|
method,
|
|
headers: {
|
|
accept: 'application/json, text/plain, */*',
|
|
...(body !== undefined && !isForm && { 'content-type': 'application/json' }),
|
|
...(token && { authorization: `Bearer ${token}` }),
|
|
...headers,
|
|
},
|
|
body: isForm ? body : body !== undefined ? JSON.stringify(body) : undefined,
|
|
...rest,
|
|
})
|
|
} catch (err) {
|
|
throw new ApiError('Unable to reach the server. Check your connection and try again.', {
|
|
status: 0,
|
|
data: err,
|
|
})
|
|
}
|
|
|
|
const text = await response.text()
|
|
let data = null
|
|
if (text) {
|
|
try {
|
|
data = JSON.parse(text)
|
|
} catch {
|
|
data = text
|
|
}
|
|
}
|
|
|
|
if (!response.ok) {
|
|
const message =
|
|
(data && typeof data === 'object' && (data.error || data.message)) ||
|
|
(typeof data === 'string' && data) ||
|
|
`Request failed (${response.status})`
|
|
throw new ApiError(message, { status: response.status, data })
|
|
}
|
|
|
|
return data
|
|
}
|