From f4067c56b4753de2125dd1bfed4c94f66c317201 Mon Sep 17 00:00:00 2001 From: Francisco Gaona Date: Mon, 22 Dec 2025 09:36:39 +0100 Subject: [PATCH] Added basic crud for objects --- frontend/components/views/EditView.vue | 12 +- frontend/components/views/ListView.vue | 8 + frontend/composables/useApi.ts | 56 +++++- frontend/composables/useFieldViews.ts | 42 +++-- frontend/pages/app/index.vue | 16 ++ .../[objectName]/[[recordId]]/[[view]].vue | 163 +++++++++++------- frontend/pages/app/objects/index.vue | 43 +++++ 7 files changed, 255 insertions(+), 85 deletions(-) create mode 100644 frontend/pages/app/index.vue create mode 100644 frontend/pages/app/objects/index.vue diff --git a/frontend/components/views/EditView.vue b/frontend/components/views/EditView.vue index bcbafc2..a4854dc 100644 --- a/frontend/components/views/EditView.vue +++ b/frontend/components/views/EditView.vue @@ -47,18 +47,22 @@ const sections = computed(() => { } // Default section with all visible fields + const visibleFields = props.config.fields + .filter(f => f.showOnEdit !== false) + .map(f => f.apiName) + return [{ title: 'Details', - fields: props.config.fields - .filter(f => f.showOnEdit !== false) - .map(f => f.apiName), + fields: visibleFields, }] }) const getFieldsBySection = (section: FieldSection) => { - return section.fields + const fields = section.fields .map(apiName => props.config.fields.find(f => f.apiName === apiName)) .filter(Boolean) + + return fields } const validateField = (field: any): string | null => { diff --git a/frontend/components/views/ListView.vue b/frontend/components/views/ListView.vue index b5dcf48..cff698e 100644 --- a/frontend/components/views/ListView.vue +++ b/frontend/components/views/ListView.vue @@ -231,4 +231,12 @@ const handleAction = (actionId: string) => { .list-view { width: 100%; } + +.list-view :deep(.border) { + background-color: hsl(var(--card)); +} + +.list-view :deep(input) { + background-color: hsl(var(--background)); +} diff --git a/frontend/composables/useApi.ts b/frontend/composables/useApi.ts index 1e42a0a..2d81686 100644 --- a/frontend/composables/useApi.ts +++ b/frontend/composables/useApi.ts @@ -63,15 +63,63 @@ export const useApi = () => { } if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`) + // Try to get error details from response + const text = await response.text() + console.error('API Error Response:', { + status: response.status, + statusText: response.statusText, + body: text + }) + + let errorMessage = `HTTP error! status: ${response.status}` + if (text) { + try { + const errorData = JSON.parse(text) + errorMessage = errorData.message || errorData.error || errorMessage + } catch (e) { + // If not JSON, use the text directly if it's not too long + if (text.length < 200) { + errorMessage = text + } + } + } + + throw new Error(errorMessage) } - return response.json() + // Handle empty responses + const text = await response.text() + if (!text) { + return {} + } + + try { + return JSON.parse(text) + } catch (e) { + console.error('Failed to parse JSON response:', text) + throw new Error('Invalid JSON response from server') + } } const api = { - async get(path: string) { - const response = await fetch(`${getApiBaseUrl()}/api${path}`, { + async get(path: string, options?: { params?: Record }) { + let url = `${getApiBaseUrl()}/api${path}` + + // Add query parameters if provided + if (options?.params) { + const searchParams = new URLSearchParams() + Object.entries(options.params).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + searchParams.append(key, String(value)) + } + }) + const queryString = searchParams.toString() + if (queryString) { + url += `?${queryString}` + } + } + + const response = await fetch(url, { headers: getHeaders(), }) return handleResponse(response) diff --git a/frontend/composables/useFieldViews.ts b/frontend/composables/useFieldViews.ts index c21dd48..a31039c 100644 --- a/frontend/composables/useFieldViews.ts +++ b/frontend/composables/useFieldViews.ts @@ -10,6 +10,12 @@ export const useFields = () => { * Convert backend field definition to frontend FieldConfig */ const mapFieldDefinitionToConfig = (fieldDef: any): FieldConfig => { + // Convert isSystem to boolean (handle 0/1 from database) + const isSystemField = Boolean(fieldDef.isSystem) + + // Only truly system fields (id, createdAt, updatedAt, etc.) should be hidden on edit + const isAutoGeneratedField = ['id', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'].includes(fieldDef.apiName) + return { id: fieldDef.id, apiName: fieldDef.apiName, @@ -23,13 +29,13 @@ export const useFields = () => { // Validation isRequired: fieldDef.isRequired, - isReadOnly: fieldDef.isSystem || fieldDef.uiMetadata?.isReadOnly, + isReadOnly: isAutoGeneratedField || fieldDef.uiMetadata?.isReadOnly, validationRules: fieldDef.uiMetadata?.validationRules || [], - // View options + // View options - only hide auto-generated fields by default showOnList: fieldDef.uiMetadata?.showOnList ?? true, showOnDetail: fieldDef.uiMetadata?.showOnDetail ?? true, - showOnEdit: fieldDef.uiMetadata?.showOnEdit ?? !fieldDef.isSystem, + showOnEdit: fieldDef.uiMetadata?.showOnEdit ?? !isAutoGeneratedField, sortable: fieldDef.uiMetadata?.sortable ?? true, // Field type specific @@ -176,14 +182,15 @@ export const useViewState = ( const saving = ref(false) const error = ref(null) - const api = useApi() + const { api } = useApi() const fetchRecords = async (params?: Record) => { loading.value = true error.value = null try { const response = await api.get(apiEndpoint, { params }) - records.value = response.data + // Handle response - data might be directly in response or in response.data + records.value = response.data || response || [] } catch (e: any) { error.value = e.message console.error('Failed to fetch records:', e) @@ -197,7 +204,8 @@ export const useViewState = ( error.value = null try { const response = await api.get(`${apiEndpoint}/${id}`) - currentRecord.value = response.data + // Handle response - data might be directly in response or in response.data + currentRecord.value = response.data || response } catch (e: any) { error.value = e.message console.error('Failed to fetch record:', e) @@ -211,9 +219,12 @@ export const useViewState = ( error.value = null try { const response = await api.post(apiEndpoint, data) - records.value.push(response.data) - currentRecord.value = response.data - return response.data + + // Handle response - it might be the data directly or wrapped in { data: ... } + const recordData = response.data || response + records.value.push(recordData) + currentRecord.value = recordData + return recordData } catch (e: any) { error.value = e.message console.error('Failed to create record:', e) @@ -227,13 +238,18 @@ export const useViewState = ( saving.value = true error.value = null try { - const response = await api.put(`${apiEndpoint}/${id}`, data) + // Remove auto-generated fields that shouldn't be updated + const { id: _id, createdAt, created_at, updatedAt, updated_at, createdBy, updatedBy, ...updateData } = data as any + + const response = await api.put(`${apiEndpoint}/${id}`, updateData) + // Handle response - data might be directly in response or in response.data + const recordData = response.data || response const idx = records.value.findIndex(r => r.id === id) if (idx !== -1) { - records.value[idx] = response.data + records.value[idx] = recordData } - currentRecord.value = response.data - return response.data + currentRecord.value = recordData + return recordData } catch (e: any) { error.value = e.message console.error('Failed to update record:', e) diff --git a/frontend/pages/app/index.vue b/frontend/pages/app/index.vue new file mode 100644 index 0000000..7f891c0 --- /dev/null +++ b/frontend/pages/app/index.vue @@ -0,0 +1,16 @@ + + + diff --git a/frontend/pages/app/objects/[objectName]/[[recordId]]/[[view]].vue b/frontend/pages/app/objects/[objectName]/[[recordId]]/[[view]].vue index 859409c..f75e65f 100644 --- a/frontend/pages/app/objects/[objectName]/[[recordId]]/[[view]].vue +++ b/frontend/pages/app/objects/[objectName]/[[recordId]]/[[view]].vue @@ -1,5 +1,5 @@