WIP - more progress with permissions
This commit is contained in:
@@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user