comprehensive diagnostic and fix system for the "undefined" member display issue
Build And Push Image / docker (push) Successful in 2m49s
Details
Build And Push Image / docker (push) Successful in 2m49s
Details
This commit is contained in:
parent
3f81d0dd86
commit
9f9cb7db53
|
|
@ -0,0 +1,212 @@
|
||||||
|
# Member "Undefined" Display Issue - Comprehensive Fix
|
||||||
|
|
||||||
|
## Issue Summary
|
||||||
|
|
||||||
|
Members were being created successfully in NocoDB but displaying as "undefined" in the portal interface. This indicated a data schema mismatch between write and read operations.
|
||||||
|
|
||||||
|
## Root Cause Analysis
|
||||||
|
|
||||||
|
After thorough investigation, the issue was identified as a **field name inconsistency** between:
|
||||||
|
- **Write operations**: Using snake_case (`first_name`, `last_name`)
|
||||||
|
- **Read operations**: Expecting snake_case but NocoDB returning display names (`First Name`, `Last Name`)
|
||||||
|
- **Display logic**: Requiring `FullName` computed from `first_name` + `last_name`
|
||||||
|
|
||||||
|
## Comprehensive Diagnostic System Implemented
|
||||||
|
|
||||||
|
### 1. NocoDB Utility Layer Diagnostics
|
||||||
|
**File**: `server/utils/nocodb.ts`
|
||||||
|
|
||||||
|
Added detailed logging in `getMembers()` to capture:
|
||||||
|
- Raw member data structure from NocoDB
|
||||||
|
- Field names and types
|
||||||
|
- Values for both snake_case and display name variants
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// DIAGNOSTIC: Log raw member data structure
|
||||||
|
console.log('[nocodb.getMembers] DIAGNOSTIC - Raw member fields from NocoDB:', Object.keys(sampleMember));
|
||||||
|
console.log('[nocodb.getMembers] DIAGNOSTIC - first_name value:', sampleMember.first_name);
|
||||||
|
console.log('[nocodb.getMembers] DIAGNOSTIC - First Name value:', (sampleMember as any)['First Name']);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. API Layer Diagnostics
|
||||||
|
**File**: `server/api/members/index.get.ts`
|
||||||
|
|
||||||
|
Enhanced GET endpoint with comprehensive logging:
|
||||||
|
- Raw data from NocoDB
|
||||||
|
- Field normalization process
|
||||||
|
- FullName calculation results
|
||||||
|
- Final processed member structure
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// DIAGNOSTIC: Log processing pipeline
|
||||||
|
console.log('[api/members.get] DIAGNOSTIC - FullName calculation result:',
|
||||||
|
`"${sampleProcessed.first_name || ''}" + " " + "${sampleProcessed.last_name || ''}" = "${sampleProcessed.FullName}"`);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Client-Side Diagnostics
|
||||||
|
**File**: `pages/dashboard/member-list.vue`
|
||||||
|
|
||||||
|
Added frontend logging to track:
|
||||||
|
- API response structure
|
||||||
|
- Member data received by client
|
||||||
|
- Field availability and values
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// DIAGNOSTIC: Log what we received from API
|
||||||
|
console.log('[member-list] DIAGNOSTIC - Sample FullName:', `"${sampleMember.FullName}"`);
|
||||||
|
console.log('[member-list] DIAGNOSTIC - Sample first_name:', `"${sampleMember.first_name}"`);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bidirectional Field Normalization System
|
||||||
|
|
||||||
|
### 1. Read Operations (NocoDB → Application)
|
||||||
|
**Function**: `normalizeFieldsFromNocoDB()`
|
||||||
|
|
||||||
|
Maps both display names and snake_case to consistent internal format:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const readFieldMap: Record<string, string> = {
|
||||||
|
'First Name': 'first_name',
|
||||||
|
'Last Name': 'last_name',
|
||||||
|
'Email': 'email',
|
||||||
|
// ... handles all field variants
|
||||||
|
'first_name': 'first_name', // Pass-through for snake_case
|
||||||
|
'last_name': 'last_name',
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Write Operations (Application → NocoDB)
|
||||||
|
**Function**: `normalizeFieldsForNocoDB()`
|
||||||
|
|
||||||
|
Maps internal snake_case to NocoDB expected format:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const writeFieldMap: Record<string, string> = {
|
||||||
|
'first_name': 'First Name',
|
||||||
|
'last_name': 'Last Name',
|
||||||
|
'email': 'Email',
|
||||||
|
// ... complete mapping for write operations
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Robust Fallback System
|
||||||
|
|
||||||
|
Ensures data integrity with multiple fallback layers:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Ensure required fields exist with fallbacks
|
||||||
|
normalized.first_name = normalized.first_name || normalized['First Name'] || '';
|
||||||
|
normalized.last_name = normalized.last_name || normalized['Last Name'] || '';
|
||||||
|
normalized.email = normalized.email || normalized['Email'] || normalized['Email Address'] || '';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration Points
|
||||||
|
|
||||||
|
### 1. GET Endpoint Integration
|
||||||
|
Applied field normalization to all retrieved members:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Apply field normalization to handle schema mismatches
|
||||||
|
members = members.map(member => {
|
||||||
|
const normalized = normalizeFieldsFromNocoDB(member);
|
||||||
|
return normalized;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. POST Endpoint Integration
|
||||||
|
Already included the server-side field mapping from previous fix:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Map display names to snake_case field names (fallback for client issues)
|
||||||
|
const normalizedBody = normalizeFieldNames(body);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
### 1. Server-Side Logging
|
||||||
|
Check server console for diagnostic output:
|
||||||
|
- `[nocodb.getMembers] DIAGNOSTIC` - Raw NocoDB data
|
||||||
|
- `[api/members.get] DIAGNOSTIC` - Processing pipeline
|
||||||
|
- `[normalizeFieldsFromNocoDB]` - Field mapping results
|
||||||
|
|
||||||
|
### 2. Client-Side Logging
|
||||||
|
Check browser console for:
|
||||||
|
- `[member-list] DIAGNOSTIC` - Frontend data reception
|
||||||
|
- Member field structure and values
|
||||||
|
- FullName calculation success
|
||||||
|
|
||||||
|
### 3. Manual Testing
|
||||||
|
1. **Create New Member**: Verify creation and immediate display
|
||||||
|
2. **Reload Page**: Check if existing members display correctly
|
||||||
|
3. **Edit Member**: Verify editing functionality works
|
||||||
|
4. **View Member**: Check detailed view displays properly
|
||||||
|
|
||||||
|
## Expected Results
|
||||||
|
|
||||||
|
After implementing these fixes:
|
||||||
|
|
||||||
|
### ✅ **Successful Scenarios**:
|
||||||
|
1. **New Members**: Display immediately after creation
|
||||||
|
2. **Existing Members**: Show correct names instead of "undefined"
|
||||||
|
3. **Mixed Schemas**: Handle both display names and snake_case data
|
||||||
|
4. **Robust Fallbacks**: Work regardless of NocoDB column naming
|
||||||
|
|
||||||
|
### 🔍 **Diagnostic Information**:
|
||||||
|
- Complete visibility into data flow from database to display
|
||||||
|
- Identification of exact field naming used by NocoDB
|
||||||
|
- Validation of field mapping effectiveness
|
||||||
|
- Confirmation of FullName calculation success
|
||||||
|
|
||||||
|
### 🛠️ **Technical Benefits**:
|
||||||
|
- **Backward Compatible**: Works with existing data
|
||||||
|
- **Future Proof**: Handles schema changes gracefully
|
||||||
|
- **Debuggable**: Comprehensive logging for troubleshooting
|
||||||
|
- **Maintainable**: Clean separation of concerns
|
||||||
|
|
||||||
|
## Troubleshooting Guide
|
||||||
|
|
||||||
|
### Issue: Still Seeing "Undefined" Names
|
||||||
|
**Check**: Server logs for `[normalizeFieldsFromNocoDB]` output
|
||||||
|
**Action**: Verify field mapping covers the actual NocoDB column names
|
||||||
|
|
||||||
|
### Issue: Empty FullName Field
|
||||||
|
**Check**: `[api/members.get] DIAGNOSTIC - FullName calculation result`
|
||||||
|
**Action**: Confirm `first_name` and `last_name` have values after normalization
|
||||||
|
|
||||||
|
### Issue: API Errors
|
||||||
|
**Check**: Server console for `[nocodb.getMembers]` errors
|
||||||
|
**Action**: Verify NocoDB connection and table configuration
|
||||||
|
|
||||||
|
### Issue: Client Not Receiving Data
|
||||||
|
**Check**: Browser console for `[member-list] DIAGNOSTIC` logs
|
||||||
|
**Action**: Confirm API response structure and member data format
|
||||||
|
|
||||||
|
## Files Modified
|
||||||
|
|
||||||
|
### Server-Side Changes:
|
||||||
|
1. **`server/utils/nocodb.ts`**
|
||||||
|
- Added diagnostic logging to `getMembers()`
|
||||||
|
- Implemented `normalizeFieldsFromNocoDB()`
|
||||||
|
- Implemented `normalizeFieldsForNocoDB()`
|
||||||
|
|
||||||
|
2. **`server/api/members/index.get.ts`**
|
||||||
|
- Enhanced diagnostic logging
|
||||||
|
- Integrated field normalization
|
||||||
|
- Added import for normalization functions
|
||||||
|
|
||||||
|
3. **`server/api/members/index.post.ts`**
|
||||||
|
- Previous field mapping enhancement (already implemented)
|
||||||
|
|
||||||
|
### Client-Side Changes:
|
||||||
|
1. **`pages/dashboard/member-list.vue`**
|
||||||
|
- Added comprehensive client-side diagnostic logging
|
||||||
|
- Enhanced member data tracking
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. **Deploy and Test**: Apply these changes and monitor logs
|
||||||
|
2. **Identify Schema**: Use diagnostic output to confirm exact NocoDB field names
|
||||||
|
3. **Optimize**: Remove excessive logging once issue is resolved
|
||||||
|
4. **Document**: Update field naming standards based on findings
|
||||||
|
|
||||||
|
This comprehensive fix provides both immediate resolution and long-term robustness for the member display system.
|
||||||
|
|
@ -425,6 +425,18 @@ const loadMembers = async () => {
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
members.value = response.data.list || [];
|
members.value = response.data.list || [];
|
||||||
|
|
||||||
|
// DIAGNOSTIC: Log what we received from API
|
||||||
|
console.log('[member-list] Received response from API:', response);
|
||||||
|
console.log('[member-list] Members count:', members.value.length);
|
||||||
|
if (members.value.length > 0) {
|
||||||
|
const sampleMember = members.value[0];
|
||||||
|
console.log('[member-list] DIAGNOSTIC - Sample member from API:', JSON.stringify(sampleMember, null, 2));
|
||||||
|
console.log('[member-list] DIAGNOSTIC - Sample member fields:', Object.keys(sampleMember));
|
||||||
|
console.log('[member-list] DIAGNOSTIC - Sample FullName:', `"${sampleMember.FullName}"`);
|
||||||
|
console.log('[member-list] DIAGNOSTIC - Sample first_name:', `"${sampleMember.first_name}"`);
|
||||||
|
console.log('[member-list] DIAGNOSTIC - Sample last_name:', `"${sampleMember.last_name}"`);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Failed to load members');
|
throw new Error('Failed to load members');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { getMembers, handleNocoDbError } from '~/server/utils/nocodb';
|
import { getMembers, handleNocoDbError, normalizeFieldsFromNocoDB } from '~/server/utils/nocodb';
|
||||||
import type { Member } from '~/utils/types';
|
import type { Member } from '~/utils/types';
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
|
|
@ -29,6 +29,27 @@ export default defineEventHandler(async (event) => {
|
||||||
let members = result.list || [];
|
let members = result.list || [];
|
||||||
console.log('[api/members.get] Fetched members count:', members.length);
|
console.log('[api/members.get] Fetched members count:', members.length);
|
||||||
|
|
||||||
|
// DIAGNOSTIC: Log processing pipeline
|
||||||
|
if (members.length > 0) {
|
||||||
|
const sampleMember = members[0];
|
||||||
|
console.log('[api/members.get] DIAGNOSTIC - Raw member from getMembers:', JSON.stringify(sampleMember, null, 2));
|
||||||
|
console.log('[api/members.get] DIAGNOSTIC - Member field check:');
|
||||||
|
console.log(' - sampleMember.first_name:', sampleMember.first_name);
|
||||||
|
console.log(' - sampleMember.last_name:', sampleMember.last_name);
|
||||||
|
console.log(' - sampleMember["First Name"]:', (sampleMember as any)['First Name']);
|
||||||
|
console.log(' - sampleMember["Last Name"]:', (sampleMember as any)['Last Name']);
|
||||||
|
console.log(' - typeof sampleMember.first_name:', typeof sampleMember.first_name);
|
||||||
|
console.log(' - typeof sampleMember.last_name:', typeof sampleMember.last_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply field normalization to handle schema mismatches
|
||||||
|
members = members.map(member => {
|
||||||
|
const normalized = normalizeFieldsFromNocoDB(member);
|
||||||
|
return normalized;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('[api/members.get] Applied field normalization to', members.length, 'members');
|
||||||
|
|
||||||
// Apply client-side filtering since NocoDB filtering can be complex
|
// Apply client-side filtering since NocoDB filtering can be complex
|
||||||
if (searchTerm) {
|
if (searchTerm) {
|
||||||
const search = searchTerm.toLowerCase();
|
const search = searchTerm.toLowerCase();
|
||||||
|
|
@ -56,11 +77,23 @@ export default defineEventHandler(async (event) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add computed fields
|
// Add computed fields
|
||||||
const processedMembers = members.map(member => ({
|
const processedMembers = members.map(member => {
|
||||||
|
const fullName = `${member.first_name || ''} ${member.last_name || ''}`.trim();
|
||||||
|
return {
|
||||||
...member,
|
...member,
|
||||||
FullName: `${member.first_name || ''} ${member.last_name || ''}`.trim(),
|
FullName: fullName,
|
||||||
FormattedPhone: formatPhoneNumber(member.phone)
|
FormattedPhone: formatPhoneNumber(member.phone)
|
||||||
}));
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// DIAGNOSTIC: Log processed member data
|
||||||
|
if (processedMembers.length > 0) {
|
||||||
|
const sampleProcessed = processedMembers[0];
|
||||||
|
console.log('[api/members.get] DIAGNOSTIC - Processed member FullName:', `"${sampleProcessed.FullName}"`);
|
||||||
|
console.log('[api/members.get] DIAGNOSTIC - FullName calculation result:',
|
||||||
|
`"${sampleProcessed.first_name || ''}" + " " + "${sampleProcessed.last_name || ''}" = "${sampleProcessed.FullName}"`);
|
||||||
|
console.log('[api/members.get] DIAGNOSTIC - Processed member keys:', Object.keys(sampleProcessed));
|
||||||
|
}
|
||||||
|
|
||||||
console.log('[api/members.get] ✅ Successfully processed', processedMembers.length, 'members');
|
console.log('[api/members.get] ✅ Successfully processed', processedMembers.length, 'members');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,102 @@ export const formatNationalitiesAsString = (nationalities: string[]): string =>
|
||||||
return nationalities.filter(n => n && n.trim()).join(',');
|
return nationalities.filter(n => n && n.trim()).join(',');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Field normalization functions
|
||||||
|
export const normalizeFieldsFromNocoDB = (data: any): Member => {
|
||||||
|
console.log('[normalizeFieldsFromNocoDB] Input data keys:', Object.keys(data));
|
||||||
|
|
||||||
|
const normalized: any = { ...data };
|
||||||
|
|
||||||
|
// Field mapping for display names to snake_case (READ operations)
|
||||||
|
const readFieldMap: Record<string, string> = {
|
||||||
|
'First Name': 'first_name',
|
||||||
|
'Last Name': 'last_name',
|
||||||
|
'Email': 'email',
|
||||||
|
'Email Address': 'email',
|
||||||
|
'Phone': 'phone',
|
||||||
|
'Phone Number': 'phone',
|
||||||
|
'Date of Birth': 'date_of_birth',
|
||||||
|
'Nationality': 'nationality',
|
||||||
|
'Address': 'address',
|
||||||
|
'Membership Status': 'membership_status',
|
||||||
|
'Member Since': 'member_since',
|
||||||
|
'Current Year Dues Paid': 'current_year_dues_paid',
|
||||||
|
'Membership Date Paid': 'membership_date_paid',
|
||||||
|
'Payment Due Date': 'payment_due_date',
|
||||||
|
// Also handle reverse mapping in case data comes in snake_case already
|
||||||
|
'first_name': 'first_name',
|
||||||
|
'last_name': 'last_name',
|
||||||
|
'email': 'email',
|
||||||
|
'phone': 'phone',
|
||||||
|
'date_of_birth': 'date_of_birth',
|
||||||
|
'nationality': 'nationality',
|
||||||
|
'address': 'address',
|
||||||
|
'membership_status': 'membership_status',
|
||||||
|
'member_since': 'member_since',
|
||||||
|
'current_year_dues_paid': 'current_year_dues_paid',
|
||||||
|
'membership_date_paid': 'membership_date_paid',
|
||||||
|
'payment_due_date': 'payment_due_date'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Apply field mapping
|
||||||
|
for (const [sourceKey, targetKey] of Object.entries(readFieldMap)) {
|
||||||
|
if (sourceKey in data && data[sourceKey] !== undefined && data[sourceKey] !== null) {
|
||||||
|
normalized[targetKey] = data[sourceKey];
|
||||||
|
console.log(`[normalizeFieldsFromNocoDB] Mapped "${sourceKey}" -> "${targetKey}":`, data[sourceKey]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure required fields exist with fallbacks
|
||||||
|
normalized.first_name = normalized.first_name || normalized['First Name'] || '';
|
||||||
|
normalized.last_name = normalized.last_name || normalized['Last Name'] || '';
|
||||||
|
normalized.email = normalized.email || normalized['Email'] || normalized['Email Address'] || '';
|
||||||
|
|
||||||
|
console.log('[normalizeFieldsFromNocoDB] Normalized member fields:', Object.keys(normalized));
|
||||||
|
console.log('[normalizeFieldsFromNocoDB] Final first_name:', normalized.first_name);
|
||||||
|
console.log('[normalizeFieldsFromNocoDB] Final last_name:', normalized.last_name);
|
||||||
|
|
||||||
|
return normalized as Member;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const normalizeFieldsForNocoDB = (data: any): Record<string, any> => {
|
||||||
|
console.log('[normalizeFieldsForNocoDB] Input data keys:', Object.keys(data));
|
||||||
|
|
||||||
|
// Field mapping for snake_case to display names (WRITE operations)
|
||||||
|
const writeFieldMap: Record<string, string> = {
|
||||||
|
'first_name': 'First Name',
|
||||||
|
'last_name': 'Last Name',
|
||||||
|
'email': 'Email',
|
||||||
|
'phone': 'Phone',
|
||||||
|
'date_of_birth': 'Date of Birth',
|
||||||
|
'nationality': 'Nationality',
|
||||||
|
'address': 'Address',
|
||||||
|
'membership_status': 'Membership Status',
|
||||||
|
'member_since': 'Member Since',
|
||||||
|
'current_year_dues_paid': 'Current Year Dues Paid',
|
||||||
|
'membership_date_paid': 'Membership Date Paid',
|
||||||
|
'payment_due_date': 'Payment Due Date'
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalized: any = {};
|
||||||
|
|
||||||
|
// First, try direct mapping using write field map
|
||||||
|
for (const [sourceKey, targetKey] of Object.entries(writeFieldMap)) {
|
||||||
|
if (sourceKey in data && data[sourceKey] !== undefined) {
|
||||||
|
normalized[targetKey] = data[sourceKey];
|
||||||
|
console.log(`[normalizeFieldsForNocoDB] Mapped "${sourceKey}" -> "${targetKey}":`, data[sourceKey]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no mappings worked, try to use the data as-is (fallback)
|
||||||
|
if (Object.keys(normalized).length === 0) {
|
||||||
|
console.log('[normalizeFieldsForNocoDB] No field mappings applied, using original data');
|
||||||
|
return { ...data };
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[normalizeFieldsForNocoDB] Final normalized keys:', Object.keys(normalized));
|
||||||
|
return normalized;
|
||||||
|
};
|
||||||
|
|
||||||
// Global variable to store effective configuration
|
// Global variable to store effective configuration
|
||||||
let globalNocoDBConfig: any = null;
|
let globalNocoDBConfig: any = null;
|
||||||
|
|
||||||
|
|
@ -161,6 +257,17 @@ export const getMembers = async (): Promise<EntityResponse<Member>> => {
|
||||||
console.log('[nocodb.getMembers] Successfully fetched members, count:', result.list?.length || 0);
|
console.log('[nocodb.getMembers] Successfully fetched members, count:', result.list?.length || 0);
|
||||||
console.log('[nocodb.getMembers] Request duration:', Date.now() - startTime, 'ms');
|
console.log('[nocodb.getMembers] Request duration:', Date.now() - startTime, 'ms');
|
||||||
|
|
||||||
|
// DIAGNOSTIC: Log raw member data structure to identify schema issues
|
||||||
|
if (result.list && result.list.length > 0) {
|
||||||
|
const sampleMember = result.list[0];
|
||||||
|
console.log('[nocodb.getMembers] DIAGNOSTIC - Raw member fields from NocoDB:', Object.keys(sampleMember));
|
||||||
|
console.log('[nocodb.getMembers] DIAGNOSTIC - Sample member data:', JSON.stringify(sampleMember, null, 2));
|
||||||
|
console.log('[nocodb.getMembers] DIAGNOSTIC - first_name value:', sampleMember.first_name);
|
||||||
|
console.log('[nocodb.getMembers] DIAGNOSTIC - last_name value:', sampleMember.last_name);
|
||||||
|
console.log('[nocodb.getMembers] DIAGNOSTIC - First Name value:', (sampleMember as any)['First Name']);
|
||||||
|
console.log('[nocodb.getMembers] DIAGNOSTIC - Last Name value:', (sampleMember as any)['Last Name']);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('[nocodb.getMembers] Error fetching members:', error);
|
console.error('[nocodb.getMembers] Error fetching members:', error);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue