WIP - more progress with permissions

This commit is contained in:
Francisco Gaona
2025-12-28 06:48:03 +01:00
parent 88f656c3f5
commit ac4a4b68cd
8 changed files with 333 additions and 91 deletions

View File

@@ -12,6 +12,8 @@ import {
CollapsibleTrigger,
} from '@/components/ui/collapsible'
console.log('[EditView] COMPONENT MOUNTING')
interface Props {
config: EditViewConfig
data?: any
@@ -25,6 +27,8 @@ const props = withDefaults(defineProps<Props>(), {
saving: false,
})
console.log('[EditView] Props received on mount:', JSON.stringify(props, null, 2))
const emit = defineEmits<{
'save': [data: any]
'cancel': []
@@ -35,10 +39,16 @@ const emit = defineEmits<{
const formData = ref<Record<string, any>>({ ...props.data })
const errors = ref<Record<string, string>>({})
console.log('[EditView] Initial props.data:', JSON.stringify(props.data, null, 2))
console.log('[EditView] props.data.id:', props.data?.id)
// Watch for data changes (useful for edit mode)
watch(() => props.data, (newData) => {
console.log('[EditView] Data changed:', JSON.stringify(newData, null, 2))
console.log('[EditView] newData.id:', newData?.id)
console.log('[EditView] Keys in newData:', Object.keys(newData))
formData.value = { ...newData }
}, { deep: true })
}, { deep: true, immediate: true })
// Organize fields into sections
const sections = computed<FieldSection[]>(() => {
@@ -137,7 +147,11 @@ const validateForm = (): boolean => {
const handleSave = () => {
if (validateForm()) {
emit('save', { ...formData.value })
// Preserve id and other system fields from original data when saving
emit('save', {
id: props.data?.id, // Preserve the record ID for updates
...formData.value
})
}
}

View File

@@ -45,11 +45,16 @@ const errors = ref<Record<string, string>>({})
// Watch for data changes (useful for edit mode)
watch(() => props.data, (newData) => {
console.log('[EditViewEnhanced] Data changed:', newData)
console.log('[EditViewEnhanced] Data has id?', newData?.id)
formData.value = { ...newData }
}, { deep: true })
}, { deep: true, immediate: true })
// Fetch page layout if objectId is provided
onMounted(async () => {
console.log('[EditViewEnhanced] Component mounted')
console.log('[EditViewEnhanced] Props:', props)
if (props.objectId) {
try {
loadingLayout.value = true
@@ -159,13 +164,27 @@ const validateForm = (): boolean => {
}
const handleSave = () => {
console.log('[EditViewEnhanced] handleSave called')
console.log('[EditViewEnhanced] props.data:', props.data)
console.log('[EditViewEnhanced] props.data?.id:', props.data?.id)
console.log('[EditViewEnhanced] formData before processing:', { ...formData.value })
if (validateForm()) {
// Filter out system fields from save data
// Preserve the id from props.data if it exists (needed for updates)
// Filter out other system fields that are auto-managed
const saveData = { ...formData.value }
const systemFields = ['id', 'tenantId', 'ownerId', 'created_at', 'updated_at', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy']
for (const field of systemFields) {
const systemFieldsToRemove = ['tenantId', 'ownerId', 'created_at', 'updated_at', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy']
for (const field of systemFieldsToRemove) {
delete saveData[field]
}
// Explicitly preserve id if it exists in the original data
if (props.data?.id) {
saveData.id = props.data.id
console.log('[EditViewEnhanced] Preserved id from props:', saveData.id)
}
console.log('[EditViewEnhanced] Final saveData:', saveData)
emit('save', saveData)
}
}

View File

@@ -22,6 +22,7 @@ interface Props {
loading?: boolean
selectable?: boolean
baseUrl?: string
canCreate?: boolean
}
const props = withDefaults(defineProps<Props>(), {
@@ -29,6 +30,7 @@ const props = withDefaults(defineProps<Props>(), {
loading: false,
selectable: false,
baseUrl: '/runtime/objects',
canCreate: true,
})
const emit = defineEmits<{
@@ -145,7 +147,7 @@ const handleAction = (actionId: string) => {
</Button>
<!-- Create -->
<Button size="sm" @click="emit('create')">
<Button v-if="props.canCreate" size="sm" @click="emit('create')">
<Plus class="h-4 w-4 mr-2" />
New
</Button>

View File

@@ -45,7 +45,9 @@ export const useApi = () => {
toast.error('Your session has expired. Please login again.')
router.push('/login')
}
throw new Error('Unauthorized')
const error = new Error('Unauthorized')
;(error as any).status = 401
throw error
}
if (response.status === 403) {
@@ -59,17 +61,24 @@ export const useApi = () => {
router.push('/login')
}
}
throw new Error('Forbidden')
// Don't log 403 errors - create error with status flag
const error = new Error('Forbidden')
;(error as any).status = 403
throw error
}
if (!response.ok) {
// 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
})
// Only log unexpected errors (not 401 or 403 which are handled above)
if (response.status !== 401 && response.status !== 403) {
console.error('API Error Response:', {
status: response.status,
statusText: response.statusText,
body: text
})
}
let errorMessage = `HTTP error! status: ${response.status}`
if (text) {

View File

@@ -197,7 +197,10 @@ export const useViewState = <T extends { id?: string }>(
records.value = response.data || response || []
} catch (e: any) {
error.value = e.message
console.error('Failed to fetch records:', e)
// Only log unexpected errors (not authorization failures)
if (e.status !== 401 && e.status !== 403) {
console.error('Failed to fetch records:', e)
}
} finally {
loading.value = false
}
@@ -210,9 +213,14 @@ export const useViewState = <T extends { id?: string }>(
const response = await api.get(`${apiEndpoint}/${id}`)
// Handle response - data might be directly in response or in response.data
currentRecord.value = response.data || response
console.log('[fetchRecord] Fetched record:', JSON.stringify(currentRecord.value, null, 2))
console.log('[fetchRecord] Record has id?', currentRecord.value?.id)
} catch (e: any) {
error.value = e.message
console.error('Failed to fetch record:', e)
// Only log unexpected errors (not authorization failures)
if (e.status !== 401 && e.status !== 403) {
console.error('Failed to fetch record:', e)
}
} finally {
loading.value = false
}
@@ -231,7 +239,7 @@ export const useViewState = <T extends { id?: string }>(
return recordData
} catch (e: any) {
error.value = e.message
console.error('Failed to create record:', e)
// Don't log to console - errors are already handled by useApi and shown via toast
throw e
} finally {
saving.value = false
@@ -256,7 +264,10 @@ export const useViewState = <T extends { id?: string }>(
return recordData
} catch (e: any) {
error.value = e.message
console.error('Failed to update record:', e)
// Only log unexpected errors (not authorization failures)
if (e.status !== 401 && e.status !== 403) {
console.error('Failed to update record:', e)
}
throw e
} finally {
saving.value = false
@@ -274,7 +285,10 @@ export const useViewState = <T extends { id?: string }>(
}
} catch (e: any) {
error.value = e.message
console.error('Failed to delete record:', e)
// Only log unexpected errors (not authorization failures)
if (e.status !== 401 && e.status !== 403) {
console.error('Failed to delete record:', e)
}
throw e
} finally {
loading.value = false
@@ -289,7 +303,10 @@ export const useViewState = <T extends { id?: string }>(
records.value = records.value.filter(r => !ids.includes(r.id!))
} catch (e: any) {
error.value = e.message
console.error('Failed to delete records:', e)
// Only log unexpected errors (not authorization failures)
if (e.status !== 401 && e.status !== 403) {
console.error('Failed to delete records:', e)
}
throw e
} finally {
loading.value = false
@@ -312,10 +329,17 @@ export const useViewState = <T extends { id?: string }>(
}
const handleSave = async (data: T) => {
// DEBUG: Check if id is present
console.log('[handleSave] Data received:', JSON.stringify(data, null, 2))
console.log('[handleSave] data.id:', data.id)
console.log('[handleSave] currentRecord.value:', currentRecord.value)
let savedRecord
if (data.id) {
console.log('[handleSave] Calling updateRecord (PUT)')
savedRecord = await updateRecord(data.id, data)
} else {
console.log('[handleSave] Calling createRecord (POST) - ID IS MISSING!')
savedRecord = await createRecord(data)
}
return savedRecord

View File

@@ -32,6 +32,7 @@ const view = computed(() => {
// State
const objectDefinition = ref<any>(null)
const objectAccess = ref<any>(null)
const loading = ref(true)
const error = ref<string | null>(null)
@@ -118,9 +119,23 @@ const detailConfig = computed(() => {
const editConfig = computed(() => {
if (!objectDefinition.value) return null
return buildEditViewConfig(objectDefinition.value)
const config = buildEditViewConfig(objectDefinition.value)
console.log('[PAGE] editConfig computed:', config ? 'EXISTS' : 'NULL')
return config
})
// Debug current view state
watch([view, recordId, editConfig, currentRecord, loading, dataLoading], ([v, rid, ec, cr, l, dl]) => {
console.log('[PAGE] View state changed:')
console.log(' - view:', v)
console.log(' - recordId:', rid)
console.log(' - editConfig exists?', !!ec)
console.log(' - currentRecord exists?', !!cr)
console.log(' - loading:', l)
console.log(' - dataLoading:', dl)
console.log(' - Should show EditView?', (v === 'edit' || rid === 'new') && !!ec)
}, { immediate: true })
// Fetch object definition
const fetchObjectDefinition = async () => {
try {
@@ -128,6 +143,21 @@ const fetchObjectDefinition = async () => {
error.value = null
const response = await api.get(`/setup/objects/${objectApiName.value}`)
objectDefinition.value = response
// Fetch access permissions
try {
const accessResponse = await api.get(`/setup/objects/${objectApiName.value}/access`)
objectAccess.value = accessResponse
} catch (e) {
console.warn('Failed to fetch access permissions:', e)
// Set defaults if fetch fails
objectAccess.value = {
publicCreate: true,
publicRead: true,
publicUpdate: true,
publicDelete: true,
}
}
} catch (e: any) {
error.value = e.message || 'Failed to load object definition'
console.error('Error fetching object definition:', e)
@@ -261,6 +291,7 @@ onMounted(async () => {
:data="records"
:loading="dataLoading"
:base-url="`/runtime/objects`"
:can-create="objectAccess?.publicCreate !== false"
selectable
@row-click="handleRowClick"
@create="handleCreate"
@@ -282,18 +313,20 @@ onMounted(async () => {
/>
<!-- Edit View -->
<EditView
v-else-if="(view === 'edit' || recordId === 'new') && editConfig"
:config="editConfig"
:data="currentRecord || {}"
:loading="dataLoading"
:saving="saving"
:object-id="objectDefinition?.id"
:base-url="`/runtime/objects`"
@save="handleSaveRecord"
@cancel="handleCancel"
@back="handleBack"
/>
<div v-else-if="(view === 'edit' || recordId === 'new') && editConfig">
<div v-if="false">DEBUG: EditView should render here. view={{ view }}, recordId={{ recordId }}, editConfig={{ !!editConfig }}, currentRecord={{ !!currentRecord }}</div>
<EditView
:config="editConfig"
:data="currentRecord || {}"
:loading="dataLoading"
:saving="saving"
:object-id="objectDefinition?.id"
:base-url="`/runtime/objects`"
@save="handleSaveRecord"
@cancel="handleCancel"
@back="handleBack"
/>
</div>
</div>
</NuxtLayout>
</template>