@visns-studio/visns-components 5.11.1 → 5.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/components/cms/DataGrid.jsx +8 -8
- package/src/components/cms/DropZone.jsx +1 -1
- package/src/components/cms/Form.jsx +1 -1
- package/src/components/cms/Gallery.jsx +1 -1
- package/src/components/cms/sorting/Item.jsx +1 -1
- package/src/components/crm/Autocomplete.jsx +3 -3
- package/src/components/crm/Breadcrumb.jsx +4 -4
- package/src/components/crm/BusinessCardOcr.jsx +681 -95
- package/src/components/crm/DataGrid.jsx +34 -32
- package/src/components/crm/Field.jsx +12 -12
- package/src/components/crm/Form.jsx +2 -2
- package/src/components/crm/Navigation.jsx +11 -11
- package/src/components/crm/Notification.jsx +2 -2
- package/src/components/crm/QuickAction.jsx +3 -3
- package/src/components/crm/SectionHeader.jsx +1 -1
- package/src/components/crm/SwitchAccount.jsx +4 -4
- package/src/components/crm/auth/ClientLogin.jsx +2 -2
- package/src/components/crm/auth/ClientOTPVerify.jsx +2 -2
- package/src/components/crm/auth/Login.jsx +3 -3
- package/src/components/crm/auth/Reset.jsx +2 -2
- package/src/components/crm/auth/TwoFactorAuth.jsx +2 -2
- package/src/components/crm/columns/ColumnRenderers.jsx +320 -259
- package/src/components/crm/controls/DataGridSearch.jsx +5 -5
- package/src/components/crm/generic/AlternativeActionModal.jsx +100 -0
- package/src/components/crm/generic/ContactSelectorModal.jsx +59 -16
- package/src/components/crm/generic/DateRangeSelectorModal.jsx +181 -0
- package/src/components/crm/generic/GenericClientPortal.jsx +1 -1
- package/src/components/crm/generic/GenericDashboard.jsx +4 -4
- package/src/components/crm/generic/GenericDetail.jsx +6 -6
- package/src/components/crm/generic/GenericDynamic.jsx +3 -3
- package/src/components/crm/generic/GenericEditableTable.jsx +4 -4
- package/src/components/crm/generic/GenericFormBuilder.jsx +6 -6
- package/src/components/crm/generic/GenericGrid.jsx +1622 -268
- package/src/components/crm/generic/GenericReport.jsx +966 -646
- package/src/components/crm/generic/GenericReportForm.jsx +1 -1
- package/src/components/crm/generic/NotificationList.jsx +1 -1
- package/src/components/crm/generic/ReasonCollectorModal.jsx +194 -0
- package/src/components/crm/generic/SortableQuoteItems.jsx +3 -3
- package/src/components/crm/generic/shared/formatters.js +107 -1
- package/src/components/crm/generic/styles/AlternativeActionModal.css +127 -0
- package/src/components/crm/generic/styles/DateRangeSelectorModal.css +115 -0
- package/src/components/crm/generic/styles/GenericIndex.module.scss +206 -0
- package/src/components/crm/generic/styles/GenericReport.module.scss +96 -0
- package/src/components/crm/generic/styles/ReasonCollectorModal.css +217 -0
- package/src/components/crm/modals/GalleryModal.jsx +2 -2
- package/src/components/crm/sorting/Item.jsx +3 -3
- package/src/components/crm/styles/BusinessCardOcr.module.scss +197 -4
|
@@ -4,30 +4,30 @@ import { toast } from 'react-toastify';
|
|
|
4
4
|
import moment from 'moment';
|
|
5
5
|
import ReactDataGrid from '@visns-studio/visns-datagrid-enterprise';
|
|
6
6
|
import {
|
|
7
|
-
Question,
|
|
8
|
-
LinkChain,
|
|
7
|
+
HelpCircle as Question,
|
|
8
|
+
Link as LinkChain,
|
|
9
9
|
Filter,
|
|
10
10
|
Download as DownloadIcon,
|
|
11
11
|
Save,
|
|
12
|
-
EyeOpen,
|
|
12
|
+
Eye as EyeOpen,
|
|
13
13
|
Info,
|
|
14
|
-
CircleCheck,
|
|
15
|
-
CirclePlus,
|
|
16
|
-
Data,
|
|
14
|
+
CheckCircle as CircleCheck,
|
|
15
|
+
PlusCircle as CirclePlus,
|
|
16
|
+
Database as Data,
|
|
17
17
|
File,
|
|
18
|
-
Person,
|
|
18
|
+
User as Person,
|
|
19
19
|
Calendar,
|
|
20
|
-
Map,
|
|
20
|
+
MapPin as Map,
|
|
21
21
|
Tag,
|
|
22
|
-
Money,
|
|
23
|
-
ShippingBoxV1,
|
|
24
|
-
SettingsVertical,
|
|
25
|
-
TriangleAlert,
|
|
26
|
-
Cart,
|
|
27
|
-
Newspaper,
|
|
22
|
+
DollarSign as Money,
|
|
23
|
+
Package as ShippingBoxV1,
|
|
24
|
+
MoreVertical as SettingsVertical,
|
|
25
|
+
AlertTriangle as TriangleAlert,
|
|
26
|
+
ShoppingCart as Cart,
|
|
27
|
+
FileText as Newspaper,
|
|
28
28
|
ChevronDown,
|
|
29
29
|
Rss,
|
|
30
|
-
} from '
|
|
30
|
+
} from 'lucide-react';
|
|
31
31
|
import CustomFetch from '../Fetch';
|
|
32
32
|
import Download from '../Download';
|
|
33
33
|
import { saveAs } from 'file-saver';
|
|
@@ -124,215 +124,6 @@ const getTableDescription = (tableName, definition) => {
|
|
|
124
124
|
return `Table: ${tableName}`;
|
|
125
125
|
};
|
|
126
126
|
|
|
127
|
-
/* This function has been moved inside renderFilters() to access state variables
|
|
128
|
-
const getSmartSuggestions = (selectedTable, joins, tableColumns, definition) => {
|
|
129
|
-
const suggestions = {
|
|
130
|
-
sorting: [],
|
|
131
|
-
filters: []
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
if (!selectedTable || !tableColumns) return suggestions;
|
|
135
|
-
|
|
136
|
-
// Get table display name for better suggestions
|
|
137
|
-
const tableDisplayName = getTableDisplayName(selectedTable, definition);
|
|
138
|
-
|
|
139
|
-
// Define smart patterns based on table types and common business logic
|
|
140
|
-
const tablePatterns = {
|
|
141
|
-
// User/Client related tables
|
|
142
|
-
users: { priority: ['name', 'created_at', 'updated_at', 'email'], filters: ['active', 'status', 'created_at'] },
|
|
143
|
-
clients: { priority: ['name', 'created_at', 'updated_at', 'email'], filters: ['active', 'status', 'created_at'] },
|
|
144
|
-
contacts: { priority: ['name', 'email', 'created_at'], filters: ['active', 'client_id'] },
|
|
145
|
-
|
|
146
|
-
// Project/Lead related tables
|
|
147
|
-
leads: { priority: ['created_at', 'updated_at', 'name', 'status'], filters: ['status', 'source', 'likelihood', 'created_at'] },
|
|
148
|
-
projects: { priority: ['created_at', 'deadline', 'name', 'status'], filters: ['status', 'priority', 'created_at'] },
|
|
149
|
-
surveys: { priority: ['created_at', 'updated_at', 'status'], filters: ['status', 'type', 'created_at'] },
|
|
150
|
-
|
|
151
|
-
// Location related tables
|
|
152
|
-
sites: { priority: ['name', 'address', 'created_at'], filters: ['active', 'state', 'client_id'] },
|
|
153
|
-
locations: { priority: ['name', 'address', 'created_at'], filters: ['active', 'state'] },
|
|
154
|
-
|
|
155
|
-
// Reference/lookup tables
|
|
156
|
-
industries: { priority: ['name', 'id'], filters: [] },
|
|
157
|
-
sources: { priority: ['name', 'id'], filters: ['active'] },
|
|
158
|
-
actions: { priority: ['name', 'priority'], filters: ['active'] },
|
|
159
|
-
tags: { priority: ['name', 'created_at'], filters: ['active'] },
|
|
160
|
-
|
|
161
|
-
// Document/content tables
|
|
162
|
-
documents: { priority: ['created_at', 'name', 'type'], filters: ['type', 'status', 'created_at'] },
|
|
163
|
-
notes: { priority: ['created_at', 'updated_at'], filters: ['created_at', 'user_id'] },
|
|
164
|
-
|
|
165
|
-
// Transactional tables
|
|
166
|
-
bookings: { priority: ['date', 'created_at', 'status'], filters: ['status', 'date', 'facility_id'] },
|
|
167
|
-
facilities: { priority: ['name', 'type', 'rate'], filters: ['type', 'available'] },
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
// Find matching pattern for current table
|
|
171
|
-
let pattern = null;
|
|
172
|
-
const lowerTableName = selectedTable.toLowerCase();
|
|
173
|
-
|
|
174
|
-
// Direct match
|
|
175
|
-
if (tablePatterns[lowerTableName]) {
|
|
176
|
-
pattern = tablePatterns[lowerTableName];
|
|
177
|
-
} else {
|
|
178
|
-
// Partial match (e.g., client_notes matches clients pattern)
|
|
179
|
-
for (const [key, value] of Object.entries(tablePatterns)) {
|
|
180
|
-
if (lowerTableName.includes(key) || key.includes(lowerTableName)) {
|
|
181
|
-
pattern = value;
|
|
182
|
-
break;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Generate sorting suggestions
|
|
188
|
-
if (pattern) {
|
|
189
|
-
// Use pattern-based priorities
|
|
190
|
-
pattern.priority.forEach((columnName, index) => {
|
|
191
|
-
const column = tableColumns.find(col =>
|
|
192
|
-
col.name.toLowerCase() === columnName.toLowerCase() ||
|
|
193
|
-
col.name.toLowerCase().includes(columnName.toLowerCase())
|
|
194
|
-
);
|
|
195
|
-
if (column) {
|
|
196
|
-
suggestions.sorting.push({
|
|
197
|
-
table: selectedTable,
|
|
198
|
-
column: column.name,
|
|
199
|
-
direction: columnName.includes('created_at') || columnName.includes('updated_at') || columnName.includes('date') ? 'DESC' : 'ASC',
|
|
200
|
-
priority: index + 1,
|
|
201
|
-
reason: `Common sorting for ${tableDisplayName}`
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
} else {
|
|
206
|
-
// Fallback: Common column patterns
|
|
207
|
-
const commonSortColumns = [
|
|
208
|
-
{ patterns: ['created_at', 'created'], direction: 'DESC', reason: 'Most recent first' },
|
|
209
|
-
{ patterns: ['updated_at', 'updated', 'modified'], direction: 'DESC', reason: 'Recently updated first' },
|
|
210
|
-
{ patterns: ['name', 'title', 'label'], direction: 'ASC', reason: 'Alphabetical order' },
|
|
211
|
-
{ patterns: ['date', 'due_date', 'deadline'], direction: 'DESC', reason: 'Latest dates first' },
|
|
212
|
-
{ patterns: ['priority', 'order', 'sequence'], direction: 'ASC', reason: 'By priority' },
|
|
213
|
-
{ patterns: ['status'], direction: 'ASC', reason: 'By status' }
|
|
214
|
-
];
|
|
215
|
-
|
|
216
|
-
commonSortColumns.forEach((sortOption, index) => {
|
|
217
|
-
const column = tableColumns.find(col =>
|
|
218
|
-
sortOption.patterns.some(pattern =>
|
|
219
|
-
col.name.toLowerCase().includes(pattern)
|
|
220
|
-
)
|
|
221
|
-
);
|
|
222
|
-
if (column) {
|
|
223
|
-
suggestions.sorting.push({
|
|
224
|
-
table: selectedTable,
|
|
225
|
-
column: column.name,
|
|
226
|
-
direction: sortOption.direction,
|
|
227
|
-
priority: index + 1,
|
|
228
|
-
reason: sortOption.reason
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Generate filter suggestions
|
|
235
|
-
if (pattern) {
|
|
236
|
-
pattern.filters.forEach(columnName => {
|
|
237
|
-
const column = tableColumns.find(col =>
|
|
238
|
-
col.name.toLowerCase() === columnName.toLowerCase() ||
|
|
239
|
-
col.name.toLowerCase().includes(columnName.toLowerCase())
|
|
240
|
-
);
|
|
241
|
-
if (column) {
|
|
242
|
-
// Determine appropriate filter based on column type and name
|
|
243
|
-
let filterType = 'equals';
|
|
244
|
-
let suggestedValue = '';
|
|
245
|
-
let reason = `Filter by ${formatName(column.name)}`;
|
|
246
|
-
|
|
247
|
-
if (column.name.toLowerCase().includes('status')) {
|
|
248
|
-
suggestedValue = 'active';
|
|
249
|
-
reason = 'Show only active records';
|
|
250
|
-
} else if (column.name.toLowerCase().includes('created_at')) {
|
|
251
|
-
filterType = 'date_range';
|
|
252
|
-
suggestedValue = 'last_30_days';
|
|
253
|
-
reason = 'Recent records (last 30 days)';
|
|
254
|
-
} else if (column.name.toLowerCase().includes('active')) {
|
|
255
|
-
suggestedValue = '1';
|
|
256
|
-
reason = 'Show only active records';
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
suggestions.filters.push({
|
|
260
|
-
table: selectedTable,
|
|
261
|
-
column: column.name,
|
|
262
|
-
operator: filterType,
|
|
263
|
-
value: suggestedValue,
|
|
264
|
-
reason: reason
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Add relationship-based suggestions
|
|
271
|
-
joins.forEach(join => {
|
|
272
|
-
if (join.availableColumns && join.availableColumns.length > 0) {
|
|
273
|
-
// Add sorting suggestions from joined tables
|
|
274
|
-
const joinTableName = join.targetTable.toLowerCase();
|
|
275
|
-
if (tablePatterns[joinTableName]) {
|
|
276
|
-
const joinPattern = tablePatterns[joinTableName];
|
|
277
|
-
joinPattern.priority.slice(0, 2).forEach((columnName, index) => {
|
|
278
|
-
const column = join.availableColumns.find(col =>
|
|
279
|
-
col.name.toLowerCase().includes(columnName.toLowerCase())
|
|
280
|
-
);
|
|
281
|
-
if (column) {
|
|
282
|
-
suggestions.sorting.push({
|
|
283
|
-
table: join.targetTable,
|
|
284
|
-
column: column.name,
|
|
285
|
-
direction: columnName.includes('created_at') || columnName.includes('date') ? 'DESC' : 'ASC',
|
|
286
|
-
priority: suggestions.sorting.length + index + 1,
|
|
287
|
-
reason: `Sort by ${getSmartRelationshipName(join.targetTable, definition)}`
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
// Limit suggestions to top 3-4 items each
|
|
296
|
-
suggestions.sorting = suggestions.sorting.slice(0, 4);
|
|
297
|
-
suggestions.filters = suggestions.filters.slice(0, 3);
|
|
298
|
-
|
|
299
|
-
// Combine all suggestions into a single array
|
|
300
|
-
const allSuggestions = [...suggestions.sorting, ...suggestions.filters];
|
|
301
|
-
|
|
302
|
-
// Check which suggestions are already applied
|
|
303
|
-
const enhancedSuggestions = allSuggestions.map(suggestion => {
|
|
304
|
-
let alreadyApplied = false;
|
|
305
|
-
|
|
306
|
-
if (suggestion.type === 'sorting') {
|
|
307
|
-
// Check if this sort criterion already exists
|
|
308
|
-
alreadyApplied = sortCriteria.some(
|
|
309
|
-
criteria => criteria.table === suggestion.table &&
|
|
310
|
-
criteria.column === suggestion.column &&
|
|
311
|
-
criteria.direction === suggestion.direction
|
|
312
|
-
);
|
|
313
|
-
} else if (suggestion.type === 'filter') {
|
|
314
|
-
// Check if this filter already exists in any group
|
|
315
|
-
alreadyApplied = filterCriteria.groups && filterCriteria.groups.some(group =>
|
|
316
|
-
group.filters.some(filter =>
|
|
317
|
-
filter.table === suggestion.table &&
|
|
318
|
-
filter.column === suggestion.column &&
|
|
319
|
-
filter.operator === suggestion.operator &&
|
|
320
|
-
filter.value === suggestion.value
|
|
321
|
-
)
|
|
322
|
-
);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
return {
|
|
326
|
-
...suggestion,
|
|
327
|
-
alreadyApplied
|
|
328
|
-
};
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
// Return prioritized suggestions (max 6-8 for clean interface)
|
|
332
|
-
return enhancedSuggestions.slice(0, 8);
|
|
333
|
-
};
|
|
334
|
-
*/
|
|
335
|
-
|
|
336
127
|
// Smart naming for suggested relationships
|
|
337
128
|
const getSmartRelationshipName = (tableName, definition) => {
|
|
338
129
|
// First try to get the defined label
|
|
@@ -1121,42 +912,225 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
1121
912
|
exportUrl = '/ajax/reportBuilder/export',
|
|
1122
913
|
} = setting;
|
|
1123
914
|
|
|
1124
|
-
//
|
|
915
|
+
// Simple icon helper using Unicode symbols for SweetAlert2
|
|
916
|
+
const getIcon = (iconName, color = '#6b7280', size = 16) => {
|
|
917
|
+
const icons = {
|
|
918
|
+
chart: '📊',
|
|
919
|
+
rocket: '🚀',
|
|
920
|
+
file: '📄',
|
|
921
|
+
cogs: '⚙️',
|
|
922
|
+
list: '📋',
|
|
923
|
+
database: '🗄️',
|
|
924
|
+
columns: '📝',
|
|
925
|
+
link: '🔗',
|
|
926
|
+
filter: '🔍',
|
|
927
|
+
download: '⬇️',
|
|
928
|
+
star: '⭐',
|
|
929
|
+
lightbulb: '💡',
|
|
930
|
+
users: '👥',
|
|
931
|
+
cart: '🛒',
|
|
932
|
+
money: '💰',
|
|
933
|
+
tasks: '✅',
|
|
934
|
+
settings: '🔧',
|
|
935
|
+
clock: '⏰',
|
|
936
|
+
question: '❓',
|
|
937
|
+
warning: '⚠️',
|
|
938
|
+
eye: '👁️'
|
|
939
|
+
};
|
|
940
|
+
|
|
941
|
+
return `<span style="color: ${color}; font-size: ${size}px; margin-right: 6px; vertical-align: middle; display: inline-block;">${icons[iconName] || '📊'}</span>`;
|
|
942
|
+
};
|
|
943
|
+
|
|
944
|
+
// Show comprehensive help guide
|
|
1125
945
|
const showGuidedHelp = () => {
|
|
1126
946
|
Swal.fire({
|
|
1127
|
-
title: '
|
|
947
|
+
title: `<div style="display: flex; align-items: center; justify-content: center; gap: 8px;">${getIcon('chart', '#3b82f6', 24)}Complete Report Builder Guide</div>`,
|
|
1128
948
|
html: `
|
|
1129
|
-
<div style="text-align: left; font-size:
|
|
1130
|
-
|
|
1131
|
-
|
|
949
|
+
<div style="text-align: left; font-size: 13px; line-height: 1.5; color: #374151; max-height: 70vh; overflow-y: auto;">
|
|
950
|
+
|
|
951
|
+
<!-- Quick Start Section -->
|
|
952
|
+
<div style="margin-bottom: 24px;">
|
|
953
|
+
<h3 style="color: #1f2937; margin: 0 0 12px 0; font-size: 16px; border-bottom: 2px solid #3b82f6; padding-bottom: 6px; display: flex; align-items: center; gap: 8px;">
|
|
954
|
+
${getIcon('rocket', '#3b82f6', 16)}
|
|
955
|
+
Getting Started
|
|
956
|
+
</h3>
|
|
957
|
+
|
|
958
|
+
<div style="background: #f0f9ff; border-left: 4px solid #3b82f6; padding: 12px; margin-bottom: 12px;">
|
|
959
|
+
<h4 style="color: #1e40af; margin: 0 0 8px 0; font-size: 14px; display: flex; align-items: center; gap: 6px;">
|
|
960
|
+
${getIcon('file', '#1e40af', 14)}
|
|
961
|
+
Option 1: Use Saved Reports (Recommended)
|
|
962
|
+
</h4>
|
|
963
|
+
<p style="margin: 0 0 8px 0;">Click the "Templates" button to access pre-built reports for common needs like client lists, recent orders, or financial summaries.</p>
|
|
964
|
+
<p style="margin: 0; font-style: italic; color: #1e40af;">Perfect for beginners and quick results!</p>
|
|
965
|
+
</div>
|
|
966
|
+
|
|
967
|
+
<div style="background: #f9fafb; border-left: 4px solid #6b7280; padding: 12px;">
|
|
968
|
+
<h4 style="color: #374151; margin: 0 0 8px 0; font-size: 14px; display: flex; align-items: center; gap: 6px;">
|
|
969
|
+
${getIcon('cogs', '#374151', 14)}
|
|
970
|
+
Option 2: Build Custom Report
|
|
971
|
+
</h4>
|
|
972
|
+
<p style="margin: 0;">Follow the 5-step wizard below to create exactly what you need.</p>
|
|
973
|
+
</div>
|
|
974
|
+
</div>
|
|
975
|
+
|
|
976
|
+
<!-- Step-by-Step Guide -->
|
|
977
|
+
<div style="margin-bottom: 24px;">
|
|
978
|
+
<h3 style="color: #1f2937; margin: 0 0 12px 0; font-size: 16px; border-bottom: 2px solid #10b981; padding-bottom: 6px; display: flex; align-items: center; gap: 8px;">
|
|
979
|
+
${getIcon('list', '#10b981', 16)}
|
|
980
|
+
5-Step Report Creation
|
|
981
|
+
</h3>
|
|
982
|
+
|
|
983
|
+
<!-- Step 1 -->
|
|
984
|
+
<div style="background: #ecfdf5; border: 1px solid #10b981; border-radius: 8px; padding: 12px; margin-bottom: 12px;">
|
|
985
|
+
<h4 style="color: #047857; margin: 0 0 8px 0; font-size: 14px; display: flex; align-items: center; gap: 6px;">
|
|
986
|
+
${getIcon('database', '#047857', 14)}
|
|
987
|
+
Step 1: Choose Your Data Source
|
|
988
|
+
</h4>
|
|
989
|
+
<ul style="margin: 0; padding-left: 16px; color: #065f46;">
|
|
990
|
+
<li>Select the main table (e.g., Clients, Orders, Invoices)</li>
|
|
991
|
+
<li>This becomes the foundation of your report</li>
|
|
992
|
+
<li>Look for the table icon to identify data types</li>
|
|
993
|
+
</ul>
|
|
994
|
+
</div>
|
|
995
|
+
|
|
996
|
+
<!-- Step 2 -->
|
|
997
|
+
<div style="background: #eff6ff; border: 1px solid #3b82f6; border-radius: 8px; padding: 12px; margin-bottom: 12px;">
|
|
998
|
+
<h4 style="color: #1e40af; margin: 0 0 8px 0; font-size: 14px; display: flex; align-items: center; gap: 6px;">
|
|
999
|
+
${getIcon('columns', '#1e40af', 14)}
|
|
1000
|
+
Step 2: Select Columns
|
|
1001
|
+
</h4>
|
|
1002
|
+
<ul style="margin: 0; padding-left: 16px; color: #1e3a8a;">
|
|
1003
|
+
<li>Check the boxes for data you want to see</li>
|
|
1004
|
+
<li>Drag columns to reorder them</li>
|
|
1005
|
+
<li>Use "Select All" for comprehensive reports</li>
|
|
1006
|
+
<li>Preview shows how your data will look</li>
|
|
1007
|
+
</ul>
|
|
1008
|
+
</div>
|
|
1009
|
+
|
|
1010
|
+
<!-- Step 3 -->
|
|
1011
|
+
<div style="background: #fef3c7; border: 1px solid #f59e0b; border-radius: 8px; padding: 12px; margin-bottom: 12px;">
|
|
1012
|
+
<h4 style="color: #92400e; margin: 0 0 8px 0; font-size: 14px; display: flex; align-items: center; gap: 6px;">
|
|
1013
|
+
${getIcon('link', '#92400e', 14)}
|
|
1014
|
+
Step 3: Add Relationships (Optional)
|
|
1015
|
+
</h4>
|
|
1016
|
+
<ul style="margin: 0; padding-left: 16px; color: #92400e;">
|
|
1017
|
+
<li>Connect related data (e.g., add customer names to invoices)</li>
|
|
1018
|
+
<li>We suggest common relationships automatically</li>
|
|
1019
|
+
<li>Click "Add Relationship" to connect more tables</li>
|
|
1020
|
+
<li>This enriches your report with additional context</li>
|
|
1021
|
+
</ul>
|
|
1022
|
+
</div>
|
|
1132
1023
|
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
<
|
|
1024
|
+
<!-- Step 4 -->
|
|
1025
|
+
<div style="background: #fdf2f8; border: 1px solid #ec4899; border-radius: 8px; padding: 12px; margin-bottom: 12px;">
|
|
1026
|
+
<h4 style="color: #be185d; margin: 0 0 8px 0; font-size: 14px; display: flex; align-items: center; gap: 6px;">
|
|
1027
|
+
${getIcon('filter', '#be185d', 14)}
|
|
1028
|
+
Step 4: Apply Filters (Optional)
|
|
1029
|
+
</h4>
|
|
1030
|
+
<ul style="margin: 0; padding-left: 16px; color: #be185d;">
|
|
1031
|
+
<li>Narrow down results (e.g., "Active clients only")</li>
|
|
1032
|
+
<li>Use AND/OR logic for complex conditions</li>
|
|
1033
|
+
<li>Group filters for advanced filtering</li>
|
|
1034
|
+
<li>Great for date ranges, status filters, etc.</li>
|
|
1035
|
+
</ul>
|
|
1136
1036
|
</div>
|
|
1137
1037
|
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
<
|
|
1038
|
+
<!-- Step 5 -->
|
|
1039
|
+
<div style="background: #f3e8ff; border: 1px solid #8b5cf6; border-radius: 8px; padding: 12px;">
|
|
1040
|
+
<h4 style="color: #7c3aed; margin: 0 0 8px 0; font-size: 14px; display: flex; align-items: center; gap: 6px;">
|
|
1041
|
+
${getIcon('download', '#7c3aed', 14)}
|
|
1042
|
+
Step 5: Generate & Export
|
|
1043
|
+
</h4>
|
|
1044
|
+
<ul style="margin: 0; padding-left: 16px; color: #7c3aed;">
|
|
1045
|
+
<li>Click "Generate Report" to see your data</li>
|
|
1046
|
+
<li>Review and refine as needed</li>
|
|
1047
|
+
<li>Export as Excel, CSV, or PDF</li>
|
|
1048
|
+
<li>Save the report template for future use</li>
|
|
1049
|
+
</ul>
|
|
1141
1050
|
</div>
|
|
1142
1051
|
</div>
|
|
1143
1052
|
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1053
|
+
<!-- Pro Tips Section -->
|
|
1054
|
+
<div style="margin-bottom: 20px;">
|
|
1055
|
+
<h3 style="color: #1f2937; margin: 0 0 12px 0; font-size: 16px; border-bottom: 2px solid #f59e0b; padding-bottom: 6px; display: flex; align-items: center; gap: 8px;">
|
|
1056
|
+
${getIcon('star', '#f59e0b', 16)}
|
|
1057
|
+
Pro Tips
|
|
1058
|
+
</h3>
|
|
1059
|
+
|
|
1060
|
+
<div style="background: #fffbeb; border: 1px solid #f59e0b; border-radius: 8px; padding: 12px;">
|
|
1061
|
+
<ul style="margin: 0; padding-left: 16px; color: #92400e;">
|
|
1062
|
+
<li><strong>Start Simple:</strong> Begin with basic reports, then add complexity</li>
|
|
1063
|
+
<li><strong>Use Previews:</strong> Check the preview before generating large reports</li>
|
|
1064
|
+
<li><strong>Save Templates:</strong> Save frequently used reports as templates</li>
|
|
1065
|
+
<li><strong>Date Filters:</strong> Use date ranges for better performance</li>
|
|
1066
|
+
<li><strong>Column Limits:</strong> Too many columns can slow down reports</li>
|
|
1067
|
+
</ul>
|
|
1068
|
+
</div>
|
|
1069
|
+
</div>
|
|
1070
|
+
|
|
1071
|
+
<!-- Common Use Cases -->
|
|
1072
|
+
<div style="margin-bottom: 20px;">
|
|
1073
|
+
<h3 style="color: #1f2937; margin: 0 0 12px 0; font-size: 16px; border-bottom: 2px solid #10b981; padding-bottom: 6px; display: flex; align-items: center; gap: 8px;">
|
|
1074
|
+
${getIcon('lightbulb', '#10b981', 16)}
|
|
1075
|
+
Common Report Examples
|
|
1076
|
+
</h3>
|
|
1077
|
+
|
|
1078
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
|
|
1079
|
+
<div style="background: #f0fdf4; border: 1px solid #22c55e; border-radius: 6px; padding: 8px;">
|
|
1080
|
+
<strong style="color: #15803d;">${getIcon('users', '#15803d', 12)}Client List:</strong>
|
|
1081
|
+
<br><small style="color: #16a34a;">Clients table + Contact details</small>
|
|
1082
|
+
</div>
|
|
1083
|
+
<div style="background: #eff6ff; border: 1px solid #3b82f6; border-radius: 6px; padding: 8px;">
|
|
1084
|
+
<strong style="color: #1d4ed8;">${getIcon('cart', '#1d4ed8', 12)}Recent Orders:</strong>
|
|
1085
|
+
<br><small style="color: #2563eb;">Orders + Customer names + Date filter</small>
|
|
1086
|
+
</div>
|
|
1087
|
+
<div style="background: #fef3c7; border: 1px solid #f59e0b; border-radius: 6px; padding: 8px;">
|
|
1088
|
+
<strong style="color: #d97706;">${getIcon('money', '#d97706', 12)}Financial Summary:</strong>
|
|
1089
|
+
<br><small style="color: #f59e0b;">Invoices + Payments + Date grouping</small>
|
|
1090
|
+
</div>
|
|
1091
|
+
<div style="background: #fdf2f8; border: 1px solid #ec4899; border-radius: 6px; padding: 8px;">
|
|
1092
|
+
<strong style="color: #db2777;">${getIcon('tasks', '#db2777', 12)}Active Projects:</strong>
|
|
1093
|
+
<br><small style="color: #ec4899;">Projects + Status filter + Team members</small>
|
|
1094
|
+
</div>
|
|
1095
|
+
</div>
|
|
1096
|
+
</div>
|
|
1097
|
+
|
|
1098
|
+
<!-- Troubleshooting -->
|
|
1099
|
+
<div>
|
|
1100
|
+
<h3 style="color: #1f2937; margin: 0 0 12px 0; font-size: 16px; border-bottom: 2px solid #dc2626; padding-bottom: 6px; display: flex; align-items: center; gap: 8px;">
|
|
1101
|
+
${getIcon('settings', '#dc2626', 16)}
|
|
1102
|
+
Common Issues
|
|
1103
|
+
</h3>
|
|
1104
|
+
|
|
1105
|
+
<div style="background: #fef2f2; border: 1px solid #dc2626; border-radius: 8px; padding: 12px;">
|
|
1106
|
+
<div style="margin-bottom: 8px;">
|
|
1107
|
+
<strong style="color: #dc2626;">${getIcon('clock', '#dc2626', 12)}Report too slow?</strong>
|
|
1108
|
+
<span style="color: #991b1b;"> Add date filters or reduce columns</span>
|
|
1109
|
+
</div>
|
|
1110
|
+
<div style="margin-bottom: 8px;">
|
|
1111
|
+
<strong style="color: #dc2626;">${getIcon('question', '#dc2626', 12)}Missing data?</strong>
|
|
1112
|
+
<span style="color: #991b1b;"> Check relationships and join conditions</span>
|
|
1113
|
+
</div>
|
|
1114
|
+
<div style="margin-bottom: 8px;">
|
|
1115
|
+
<strong style="color: #dc2626;">${getIcon('warning', '#dc2626', 12)}Export failing?</strong>
|
|
1116
|
+
<span style="color: #991b1b;"> Try smaller date ranges or fewer rows</span>
|
|
1117
|
+
</div>
|
|
1118
|
+
<div>
|
|
1119
|
+
<strong style="color: #dc2626;">${getIcon('eye', '#dc2626', 12)}No data showing?</strong>
|
|
1120
|
+
<span style="color: #991b1b;"> Review your filter conditions</span>
|
|
1121
|
+
</div>
|
|
1122
|
+
</div>
|
|
1148
1123
|
</div>
|
|
1149
1124
|
</div>
|
|
1150
1125
|
`,
|
|
1151
|
-
width:
|
|
1152
|
-
confirmButtonText: "
|
|
1153
|
-
confirmButtonColor: '#
|
|
1154
|
-
showCancelButton:
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
setShowTemplates(true);
|
|
1126
|
+
width: 800,
|
|
1127
|
+
confirmButtonText: "Close",
|
|
1128
|
+
confirmButtonColor: '#6b7280',
|
|
1129
|
+
showCancelButton: false,
|
|
1130
|
+
showDenyButton: false,
|
|
1131
|
+
customClass: {
|
|
1132
|
+
popup: 'comprehensive-help-modal',
|
|
1133
|
+
htmlContainer: 'help-content-scrollable'
|
|
1160
1134
|
}
|
|
1161
1135
|
});
|
|
1162
1136
|
};
|
|
@@ -1743,7 +1717,7 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
1743
1717
|
} else {
|
|
1744
1718
|
// Handle the response format similar to fetchTableColumns
|
|
1745
1719
|
let columnsData = [];
|
|
1746
|
-
|
|
1720
|
+
|
|
1747
1721
|
if (result.data?.success && result.data?.data?.columns) {
|
|
1748
1722
|
// Primary format: {"success":true,"data":{"table":"contacts_tags","columns":[...]}}
|
|
1749
1723
|
columnsData = result.data.data.columns;
|
|
@@ -1767,10 +1741,13 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
1767
1741
|
columnsData = fallback;
|
|
1768
1742
|
}
|
|
1769
1743
|
}
|
|
1770
|
-
|
|
1744
|
+
|
|
1771
1745
|
// Ensure columnsData is always an array
|
|
1772
1746
|
if (!Array.isArray(columnsData)) {
|
|
1773
|
-
console.error(
|
|
1747
|
+
console.error(
|
|
1748
|
+
`Invalid columns data format for ${type} table:`,
|
|
1749
|
+
result
|
|
1750
|
+
);
|
|
1774
1751
|
columnsData = [];
|
|
1775
1752
|
}
|
|
1776
1753
|
|
|
@@ -1884,12 +1861,14 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
1884
1861
|
column: suggestion.column,
|
|
1885
1862
|
direction: suggestion.direction,
|
|
1886
1863
|
};
|
|
1887
|
-
|
|
1864
|
+
|
|
1888
1865
|
// Check if this sort already exists
|
|
1889
1866
|
const existingIndex = sortCriteria.findIndex(
|
|
1890
|
-
(criteria) =>
|
|
1867
|
+
(criteria) =>
|
|
1868
|
+
criteria.table === suggestion.table &&
|
|
1869
|
+
criteria.column === suggestion.column
|
|
1891
1870
|
);
|
|
1892
|
-
|
|
1871
|
+
|
|
1893
1872
|
if (existingIndex >= 0) {
|
|
1894
1873
|
// Update existing sort
|
|
1895
1874
|
const updatedSort = [...sortCriteria];
|
|
@@ -1898,7 +1877,7 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
1898
1877
|
toast.info(`Updated existing sort: ${suggestion.title}`);
|
|
1899
1878
|
} else {
|
|
1900
1879
|
// Add new sort
|
|
1901
|
-
setSortCriteria(prev => [...prev, newSortCriterion]);
|
|
1880
|
+
setSortCriteria((prev) => [...prev, newSortCriterion]);
|
|
1902
1881
|
toast.success(`Applied sorting: ${suggestion.title}`);
|
|
1903
1882
|
}
|
|
1904
1883
|
} else if (suggestion.type === 'filter') {
|
|
@@ -1909,24 +1888,29 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
1909
1888
|
operator: suggestion.operator,
|
|
1910
1889
|
value: suggestion.value,
|
|
1911
1890
|
};
|
|
1912
|
-
|
|
1913
|
-
setFilterCriteria(prev => {
|
|
1891
|
+
|
|
1892
|
+
setFilterCriteria((prev) => {
|
|
1914
1893
|
const updatedCriteria = { ...prev };
|
|
1915
|
-
|
|
1894
|
+
|
|
1916
1895
|
// Ensure we have at least one group
|
|
1917
|
-
if (
|
|
1918
|
-
updatedCriteria.groups
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1896
|
+
if (
|
|
1897
|
+
!updatedCriteria.groups ||
|
|
1898
|
+
updatedCriteria.groups.length === 0
|
|
1899
|
+
) {
|
|
1900
|
+
updatedCriteria.groups = [
|
|
1901
|
+
{
|
|
1902
|
+
operator: 'AND',
|
|
1903
|
+
filters: [],
|
|
1904
|
+
},
|
|
1905
|
+
];
|
|
1922
1906
|
}
|
|
1923
|
-
|
|
1907
|
+
|
|
1924
1908
|
// Add to first group
|
|
1925
1909
|
updatedCriteria.groups[0].filters.push(newFilterCriterion);
|
|
1926
|
-
|
|
1910
|
+
|
|
1927
1911
|
return updatedCriteria;
|
|
1928
1912
|
});
|
|
1929
|
-
|
|
1913
|
+
|
|
1930
1914
|
toast.success(`Applied filter: ${suggestion.title}`);
|
|
1931
1915
|
}
|
|
1932
1916
|
};
|
|
@@ -3117,83 +3101,94 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
3117
3101
|
<div className={styles.detectedJoins}>
|
|
3118
3102
|
<h3>Detected Connections</h3>
|
|
3119
3103
|
<p>
|
|
3120
|
-
We found these possible connections to other data
|
|
3104
|
+
We found these possible connections to other data
|
|
3105
|
+
source:
|
|
3121
3106
|
</p>
|
|
3122
3107
|
<div className={styles.detectionsGrid}>
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
{
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3108
|
+
{tableSuggestions.map((suggestion, index) => (
|
|
3109
|
+
<div
|
|
3110
|
+
key={index}
|
|
3111
|
+
className={styles.detectionCard}
|
|
3112
|
+
>
|
|
3113
|
+
<div className={styles.detectionInfo}>
|
|
3114
|
+
<div className={styles.detectionTitle}>
|
|
3115
|
+
<LinkChain size={16} />
|
|
3116
|
+
<span>
|
|
3117
|
+
Connect to{' '}
|
|
3118
|
+
{getSmartRelationshipName(
|
|
3119
|
+
suggestion.targetTable,
|
|
3120
|
+
definition
|
|
3121
|
+
)}
|
|
3122
|
+
</span>
|
|
3123
|
+
<div
|
|
3124
|
+
className={
|
|
3125
|
+
styles.confidenceRating
|
|
3126
|
+
}
|
|
3127
|
+
>
|
|
3128
|
+
{'⭐'.repeat(
|
|
3129
|
+
Math.floor(
|
|
3130
|
+
suggestion.confidence /
|
|
3131
|
+
20
|
|
3132
|
+
)
|
|
3133
|
+
)}
|
|
3134
|
+
</div>
|
|
3143
3135
|
</div>
|
|
3136
|
+
<p
|
|
3137
|
+
className={
|
|
3138
|
+
styles.detectionDescription
|
|
3139
|
+
}
|
|
3140
|
+
>
|
|
3141
|
+
{suggestion.description ||
|
|
3142
|
+
`Link ${getTableDisplayName(
|
|
3143
|
+
selectedTable,
|
|
3144
|
+
definition
|
|
3145
|
+
)} with ${getTableDisplayName(
|
|
3146
|
+
suggestion.targetTable,
|
|
3147
|
+
definition
|
|
3148
|
+
)}`}
|
|
3149
|
+
</p>
|
|
3144
3150
|
</div>
|
|
3145
|
-
<
|
|
3146
|
-
{
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
className={`${styles.connectIconBtn} ${
|
|
3158
|
-
joins.some(
|
|
3151
|
+
<button
|
|
3152
|
+
className={`${styles.connectIconBtn} ${
|
|
3153
|
+
joins.some(
|
|
3154
|
+
(j) =>
|
|
3155
|
+
j.targetTable ===
|
|
3156
|
+
suggestion.targetTable
|
|
3157
|
+
)
|
|
3158
|
+
? styles.connected
|
|
3159
|
+
: ''
|
|
3160
|
+
}`}
|
|
3161
|
+
onClick={() => addJoin(suggestion)}
|
|
3162
|
+
disabled={joins.some(
|
|
3159
3163
|
(j) =>
|
|
3160
3164
|
j.targetTable ===
|
|
3161
3165
|
suggestion.targetTable
|
|
3162
|
-
)
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3166
|
+
)}
|
|
3167
|
+
title={
|
|
3168
|
+
joins.some(
|
|
3169
|
+
(j) =>
|
|
3170
|
+
j.targetTable ===
|
|
3171
|
+
suggestion.targetTable
|
|
3172
|
+
)
|
|
3173
|
+
? 'Already connected'
|
|
3174
|
+
: `Connect to ${getSmartRelationshipName(
|
|
3175
|
+
suggestion.targetTable,
|
|
3176
|
+
definition
|
|
3177
|
+
)}`
|
|
3178
|
+
}
|
|
3179
|
+
>
|
|
3180
|
+
{joins.some(
|
|
3174
3181
|
(j) =>
|
|
3175
3182
|
j.targetTable ===
|
|
3176
3183
|
suggestion.targetTable
|
|
3177
|
-
)
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
{joins.some(
|
|
3186
|
-
(j) =>
|
|
3187
|
-
j.targetTable ===
|
|
3188
|
-
suggestion.targetTable
|
|
3189
|
-
) ? (
|
|
3190
|
-
<CircleCheck size={20} />
|
|
3191
|
-
) : (
|
|
3192
|
-
<LinkChain size={20} />
|
|
3193
|
-
)}
|
|
3194
|
-
</button>
|
|
3195
|
-
</div>
|
|
3196
|
-
))}
|
|
3184
|
+
) ? (
|
|
3185
|
+
<CircleCheck size={20} />
|
|
3186
|
+
) : (
|
|
3187
|
+
<LinkChain size={20} />
|
|
3188
|
+
)}
|
|
3189
|
+
</button>
|
|
3190
|
+
</div>
|
|
3191
|
+
))}
|
|
3197
3192
|
</div>
|
|
3198
3193
|
</div>
|
|
3199
3194
|
)}
|
|
@@ -3214,7 +3209,11 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
3214
3209
|
<div className={styles.advancedWarning}>
|
|
3215
3210
|
<TriangleAlert size={16} />
|
|
3216
3211
|
<span>
|
|
3217
|
-
<strong>Advanced Feature:</strong> Manual
|
|
3212
|
+
<strong>Advanced Feature:</strong> Manual
|
|
3213
|
+
relationships require understanding of database
|
|
3214
|
+
table structures and foreign key relationships. Only
|
|
3215
|
+
create manual relationships if you understand how
|
|
3216
|
+
database tables connect.
|
|
3218
3217
|
</span>
|
|
3219
3218
|
</div>
|
|
3220
3219
|
|
|
@@ -3314,14 +3313,19 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
3314
3313
|
disabled={!manualJoin.sourceTable}
|
|
3315
3314
|
>
|
|
3316
3315
|
<option value="">Select Column</option>
|
|
3317
|
-
{Array.isArray(
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3316
|
+
{Array.isArray(
|
|
3317
|
+
manualJoinColumns.source
|
|
3318
|
+
) &&
|
|
3319
|
+
manualJoinColumns.source.map(
|
|
3320
|
+
(col) => (
|
|
3321
|
+
<option
|
|
3322
|
+
key={col.name}
|
|
3323
|
+
value={col.name}
|
|
3324
|
+
>
|
|
3325
|
+
{formatName(col.name)}
|
|
3326
|
+
</option>
|
|
3327
|
+
)
|
|
3328
|
+
)}
|
|
3325
3329
|
</select>
|
|
3326
3330
|
</div>
|
|
3327
3331
|
|
|
@@ -3373,14 +3377,19 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
3373
3377
|
disabled={!manualJoin.targetTable}
|
|
3374
3378
|
>
|
|
3375
3379
|
<option value="">Select Column</option>
|
|
3376
|
-
{Array.isArray(
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3380
|
+
{Array.isArray(
|
|
3381
|
+
manualJoinColumns.target
|
|
3382
|
+
) &&
|
|
3383
|
+
manualJoinColumns.target.map(
|
|
3384
|
+
(col) => (
|
|
3385
|
+
<option
|
|
3386
|
+
key={col.name}
|
|
3387
|
+
value={col.name}
|
|
3388
|
+
>
|
|
3389
|
+
{formatName(col.name)}
|
|
3390
|
+
</option>
|
|
3391
|
+
)
|
|
3392
|
+
)}
|
|
3384
3393
|
</select>
|
|
3385
3394
|
</div>
|
|
3386
3395
|
</div>
|
|
@@ -3763,101 +3772,254 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
3763
3772
|
// Advanced filter rendering (from original GenericReport)
|
|
3764
3773
|
const renderFilters = () => {
|
|
3765
3774
|
const availableColumns = getAvailableFilterColumns();
|
|
3766
|
-
|
|
3775
|
+
|
|
3767
3776
|
// Smart suggestions for sorting and filtering based on table type and relationships
|
|
3768
3777
|
const getSmartSuggestions = () => {
|
|
3769
3778
|
const suggestions = {
|
|
3770
3779
|
sorting: [],
|
|
3771
|
-
filters: []
|
|
3780
|
+
filters: [],
|
|
3772
3781
|
};
|
|
3773
3782
|
|
|
3774
3783
|
if (!selectedTable || !tableColumns) return [];
|
|
3775
3784
|
|
|
3776
3785
|
// Get table display name for better suggestions
|
|
3777
|
-
const tableDisplayName = getTableDisplayName(
|
|
3778
|
-
|
|
3786
|
+
const tableDisplayName = getTableDisplayName(
|
|
3787
|
+
selectedTable,
|
|
3788
|
+
definition
|
|
3789
|
+
);
|
|
3790
|
+
|
|
3779
3791
|
// Define smart patterns based on table types and common business logic
|
|
3780
3792
|
const tablePatterns = {
|
|
3781
3793
|
// User/Client related tables
|
|
3782
|
-
users: {
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3794
|
+
users: {
|
|
3795
|
+
priority: ['name', 'created_at', 'updated_at', 'email'],
|
|
3796
|
+
filters: [
|
|
3797
|
+
'active',
|
|
3798
|
+
'status',
|
|
3799
|
+
'created_at',
|
|
3800
|
+
'email',
|
|
3801
|
+
'role',
|
|
3802
|
+
],
|
|
3803
|
+
},
|
|
3804
|
+
clients: {
|
|
3805
|
+
priority: ['name', 'created_at', 'updated_at', 'email'],
|
|
3806
|
+
filters: [
|
|
3807
|
+
'active',
|
|
3808
|
+
'status',
|
|
3809
|
+
'created_at',
|
|
3810
|
+
'email',
|
|
3811
|
+
'phone',
|
|
3812
|
+
'type',
|
|
3813
|
+
],
|
|
3814
|
+
},
|
|
3815
|
+
contacts: {
|
|
3816
|
+
priority: ['name', 'email', 'created_at'],
|
|
3817
|
+
filters: [
|
|
3818
|
+
'active',
|
|
3819
|
+
'client_id',
|
|
3820
|
+
'email',
|
|
3821
|
+
'phone',
|
|
3822
|
+
'created_at',
|
|
3823
|
+
],
|
|
3824
|
+
},
|
|
3825
|
+
|
|
3786
3826
|
// Project/Lead related tables
|
|
3787
|
-
leads: {
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3827
|
+
leads: {
|
|
3828
|
+
priority: ['created_at', 'updated_at', 'name', 'status'],
|
|
3829
|
+
filters: [
|
|
3830
|
+
'status',
|
|
3831
|
+
'source',
|
|
3832
|
+
'priority',
|
|
3833
|
+
'created_at',
|
|
3834
|
+
'assigned_to',
|
|
3835
|
+
'email',
|
|
3836
|
+
],
|
|
3837
|
+
},
|
|
3838
|
+
projects: {
|
|
3839
|
+
priority: ['created_at', 'deadline', 'name', 'status'],
|
|
3840
|
+
filters: [
|
|
3841
|
+
'status',
|
|
3842
|
+
'priority',
|
|
3843
|
+
'created_at',
|
|
3844
|
+
'due_date',
|
|
3845
|
+
'client_id',
|
|
3846
|
+
'assigned_to',
|
|
3847
|
+
],
|
|
3848
|
+
},
|
|
3849
|
+
tasks: {
|
|
3850
|
+
priority: ['created_at', 'deadline', 'priority'],
|
|
3851
|
+
filters: [
|
|
3852
|
+
'status',
|
|
3853
|
+
'priority',
|
|
3854
|
+
'assigned_to',
|
|
3855
|
+
'created_at',
|
|
3856
|
+
'due_date',
|
|
3857
|
+
],
|
|
3858
|
+
},
|
|
3859
|
+
surveys: {
|
|
3860
|
+
priority: ['created_at', 'updated_at', 'status'],
|
|
3861
|
+
filters: ['status', 'type', 'created_at', 'active'],
|
|
3862
|
+
},
|
|
3863
|
+
|
|
3792
3864
|
// Location related tables
|
|
3793
|
-
sites: {
|
|
3794
|
-
|
|
3795
|
-
|
|
3865
|
+
sites: {
|
|
3866
|
+
priority: ['name', 'address', 'created_at'],
|
|
3867
|
+
filters: ['active', 'state', 'client_id', 'type', 'status'],
|
|
3868
|
+
},
|
|
3869
|
+
locations: {
|
|
3870
|
+
priority: ['name', 'address', 'created_at'],
|
|
3871
|
+
filters: ['active', 'state', 'type', 'status'],
|
|
3872
|
+
},
|
|
3873
|
+
|
|
3796
3874
|
// Reference/lookup tables
|
|
3797
3875
|
industries: { priority: ['name', 'id'], filters: ['active'] },
|
|
3798
|
-
sources: {
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3876
|
+
sources: {
|
|
3877
|
+
priority: ['name', 'id'],
|
|
3878
|
+
filters: ['active', 'type'],
|
|
3879
|
+
},
|
|
3880
|
+
actions: {
|
|
3881
|
+
priority: ['name', 'priority'],
|
|
3882
|
+
filters: ['active', 'priority', 'type'],
|
|
3883
|
+
},
|
|
3884
|
+
tags: {
|
|
3885
|
+
priority: ['name', 'created_at'],
|
|
3886
|
+
filters: ['active', 'type', 'category'],
|
|
3887
|
+
},
|
|
3888
|
+
|
|
3802
3889
|
// Document/content tables
|
|
3803
|
-
documents: {
|
|
3804
|
-
|
|
3805
|
-
|
|
3890
|
+
documents: {
|
|
3891
|
+
priority: ['created_at', 'name', 'type'],
|
|
3892
|
+
filters: [
|
|
3893
|
+
'type',
|
|
3894
|
+
'status',
|
|
3895
|
+
'created_at',
|
|
3896
|
+
'category',
|
|
3897
|
+
'owner_id',
|
|
3898
|
+
],
|
|
3899
|
+
},
|
|
3900
|
+
notes: {
|
|
3901
|
+
priority: ['created_at', 'updated_at'],
|
|
3902
|
+
filters: ['created_at', 'user_id', 'type', 'priority'],
|
|
3903
|
+
},
|
|
3904
|
+
|
|
3806
3905
|
// Communication tables
|
|
3807
|
-
emails: {
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3906
|
+
emails: {
|
|
3907
|
+
priority: ['created_at', 'subject'],
|
|
3908
|
+
filters: [
|
|
3909
|
+
'status',
|
|
3910
|
+
'created_at',
|
|
3911
|
+
'from_email',
|
|
3912
|
+
'to_email',
|
|
3913
|
+
'type',
|
|
3914
|
+
],
|
|
3915
|
+
},
|
|
3916
|
+
calls: {
|
|
3917
|
+
priority: ['created_at', 'duration'],
|
|
3918
|
+
filters: ['status', 'created_at', 'type', 'outcome'],
|
|
3919
|
+
},
|
|
3920
|
+
messages: {
|
|
3921
|
+
priority: ['created_at', 'subject'],
|
|
3922
|
+
filters: ['status', 'created_at', 'type', 'priority'],
|
|
3923
|
+
},
|
|
3924
|
+
|
|
3811
3925
|
// Transactional tables
|
|
3812
|
-
orders: {
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3926
|
+
orders: {
|
|
3927
|
+
priority: ['created_at', 'status', 'total'],
|
|
3928
|
+
filters: ['status', 'created_at', 'client_id', 'type'],
|
|
3929
|
+
},
|
|
3930
|
+
invoices: {
|
|
3931
|
+
priority: ['created_at', 'due_date', 'status'],
|
|
3932
|
+
filters: ['status', 'created_at', 'due_date', 'client_id'],
|
|
3933
|
+
},
|
|
3934
|
+
bookings: {
|
|
3935
|
+
priority: ['date', 'created_at', 'status'],
|
|
3936
|
+
filters: [
|
|
3937
|
+
'status',
|
|
3938
|
+
'date',
|
|
3939
|
+
'created_at',
|
|
3940
|
+
'client_id',
|
|
3941
|
+
'type',
|
|
3942
|
+
],
|
|
3943
|
+
},
|
|
3944
|
+
facilities: {
|
|
3945
|
+
priority: ['name', 'type', 'rate'],
|
|
3946
|
+
filters: ['type', 'active', 'status', 'category'],
|
|
3947
|
+
},
|
|
3948
|
+
|
|
3817
3949
|
// Financial tables
|
|
3818
|
-
payments: {
|
|
3819
|
-
|
|
3950
|
+
payments: {
|
|
3951
|
+
priority: ['created_at', 'amount', 'status'],
|
|
3952
|
+
filters: ['status', 'created_at', 'method', 'type'],
|
|
3953
|
+
},
|
|
3954
|
+
transactions: {
|
|
3955
|
+
priority: ['created_at', 'amount', 'type'],
|
|
3956
|
+
filters: ['type', 'status', 'created_at', 'category'],
|
|
3957
|
+
},
|
|
3820
3958
|
};
|
|
3821
3959
|
|
|
3822
3960
|
// Get the pattern for this table or fall back to generic patterns
|
|
3823
3961
|
const pattern = tablePatterns[selectedTable] || {
|
|
3824
3962
|
priority: ['created_at', 'updated_at', 'name', 'title', 'id'],
|
|
3825
|
-
filters: ['status', 'active', 'created_at']
|
|
3963
|
+
filters: ['status', 'active', 'created_at'],
|
|
3826
3964
|
};
|
|
3827
3965
|
|
|
3828
3966
|
// Helper function to check if column exists in the table
|
|
3829
3967
|
const columnExists = (columnName, table = selectedTable) => {
|
|
3830
3968
|
if (table === selectedTable) {
|
|
3831
|
-
return tableColumns.some(
|
|
3969
|
+
return tableColumns.some(
|
|
3970
|
+
(col) =>
|
|
3971
|
+
col.name.toLowerCase() === columnName.toLowerCase()
|
|
3972
|
+
);
|
|
3832
3973
|
}
|
|
3833
3974
|
// Check joined tables
|
|
3834
|
-
const join = joins.find(j => j.targetTable === table);
|
|
3835
|
-
return
|
|
3975
|
+
const join = joins.find((j) => j.targetTable === table);
|
|
3976
|
+
return (
|
|
3977
|
+
join &&
|
|
3978
|
+
join.availableColumns &&
|
|
3979
|
+
join.availableColumns.some(
|
|
3980
|
+
(col) =>
|
|
3981
|
+
col.name.toLowerCase() === columnName.toLowerCase()
|
|
3982
|
+
)
|
|
3983
|
+
);
|
|
3836
3984
|
};
|
|
3837
3985
|
|
|
3838
3986
|
// Generate sorting suggestions
|
|
3839
3987
|
pattern.priority.forEach((columnName) => {
|
|
3840
3988
|
if (columnExists(columnName)) {
|
|
3841
|
-
const isDateColumn = [
|
|
3989
|
+
const isDateColumn = [
|
|
3990
|
+
'created_at',
|
|
3991
|
+
'updated_at',
|
|
3992
|
+
'date',
|
|
3993
|
+
'deadline',
|
|
3994
|
+
'due_date',
|
|
3995
|
+
].includes(columnName.toLowerCase());
|
|
3842
3996
|
const direction = isDateColumn ? 'desc' : 'asc';
|
|
3843
|
-
|
|
3997
|
+
|
|
3844
3998
|
suggestions.sorting.push({
|
|
3845
3999
|
type: 'sorting',
|
|
3846
4000
|
table: selectedTable,
|
|
3847
4001
|
column: columnName,
|
|
3848
4002
|
direction: direction,
|
|
3849
|
-
title: `Sort by ${formatName(columnName)} (${
|
|
3850
|
-
|
|
3851
|
-
|
|
4003
|
+
title: `Sort by ${formatName(columnName)} (${
|
|
4004
|
+
direction === 'desc' ? 'Newest First' : 'A-Z'
|
|
4005
|
+
})`,
|
|
4006
|
+
description: `Order results by ${formatName(
|
|
4007
|
+
columnName
|
|
4008
|
+
)} in ${
|
|
4009
|
+
direction === 'desc' ? 'descending' : 'ascending'
|
|
4010
|
+
} order`,
|
|
4011
|
+
reasoning: isDateColumn
|
|
4012
|
+
? 'Show most recent records first'
|
|
4013
|
+
: 'Alphabetical ordering for easy browsing',
|
|
3852
4014
|
});
|
|
3853
4015
|
}
|
|
3854
4016
|
});
|
|
3855
4017
|
|
|
3856
4018
|
// Generate filter suggestions
|
|
3857
|
-
pattern.filters.forEach(columnName => {
|
|
4019
|
+
pattern.filters.forEach((columnName) => {
|
|
3858
4020
|
if (columnExists(columnName)) {
|
|
3859
4021
|
const columnLower = columnName.toLowerCase();
|
|
3860
|
-
|
|
4022
|
+
|
|
3861
4023
|
// Special handling for common filter types
|
|
3862
4024
|
if (['active', 'status'].includes(columnLower)) {
|
|
3863
4025
|
// Active/Status filters
|
|
@@ -3870,43 +4032,52 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
3870
4032
|
value: '1',
|
|
3871
4033
|
title: 'Show only active records',
|
|
3872
4034
|
description: `Filter to show only active ${tableDisplayName.toLowerCase()}`,
|
|
3873
|
-
reasoning:
|
|
4035
|
+
reasoning:
|
|
4036
|
+
'Focus on currently relevant records',
|
|
3874
4037
|
});
|
|
3875
4038
|
} else if (columnLower === 'status') {
|
|
3876
4039
|
// Multiple status suggestions based on table type
|
|
3877
4040
|
const statusSuggestions = [];
|
|
3878
|
-
|
|
3879
|
-
if (
|
|
4041
|
+
|
|
4042
|
+
if (
|
|
4043
|
+
['leads', 'projects', 'tasks'].includes(
|
|
4044
|
+
selectedTable.toLowerCase()
|
|
4045
|
+
)
|
|
4046
|
+
) {
|
|
3880
4047
|
statusSuggestions.push({
|
|
3881
4048
|
value: 'active',
|
|
3882
4049
|
title: 'Show active items',
|
|
3883
|
-
description: `Show active ${tableDisplayName.toLowerCase()}
|
|
4050
|
+
description: `Show active ${tableDisplayName.toLowerCase()}`,
|
|
3884
4051
|
});
|
|
3885
4052
|
statusSuggestions.push({
|
|
3886
4053
|
value: 'pending',
|
|
3887
4054
|
title: 'Show pending items',
|
|
3888
|
-
description: `Show pending ${tableDisplayName.toLowerCase()}
|
|
4055
|
+
description: `Show pending ${tableDisplayName.toLowerCase()}`,
|
|
3889
4056
|
});
|
|
3890
|
-
} else if (
|
|
4057
|
+
} else if (
|
|
4058
|
+
['orders', 'bookings', 'invoices'].includes(
|
|
4059
|
+
selectedTable.toLowerCase()
|
|
4060
|
+
)
|
|
4061
|
+
) {
|
|
3891
4062
|
statusSuggestions.push({
|
|
3892
4063
|
value: 'confirmed',
|
|
3893
4064
|
title: 'Show confirmed items',
|
|
3894
|
-
description: `Show confirmed ${tableDisplayName.toLowerCase()}
|
|
4065
|
+
description: `Show confirmed ${tableDisplayName.toLowerCase()}`,
|
|
3895
4066
|
});
|
|
3896
4067
|
statusSuggestions.push({
|
|
3897
4068
|
value: 'pending',
|
|
3898
4069
|
title: 'Show pending items',
|
|
3899
|
-
description: `Show pending ${tableDisplayName.toLowerCase()}
|
|
4070
|
+
description: `Show pending ${tableDisplayName.toLowerCase()}`,
|
|
3900
4071
|
});
|
|
3901
4072
|
} else {
|
|
3902
4073
|
statusSuggestions.push({
|
|
3903
4074
|
value: 'active',
|
|
3904
4075
|
title: 'Show active records',
|
|
3905
|
-
description: `Show active ${tableDisplayName.toLowerCase()}
|
|
4076
|
+
description: `Show active ${tableDisplayName.toLowerCase()}`,
|
|
3906
4077
|
});
|
|
3907
4078
|
}
|
|
3908
|
-
|
|
3909
|
-
statusSuggestions.forEach(statusSug => {
|
|
4079
|
+
|
|
4080
|
+
statusSuggestions.forEach((statusSug) => {
|
|
3910
4081
|
suggestions.filters.push({
|
|
3911
4082
|
type: 'filter',
|
|
3912
4083
|
table: selectedTable,
|
|
@@ -3915,51 +4086,71 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
3915
4086
|
value: statusSug.value,
|
|
3916
4087
|
title: statusSug.title,
|
|
3917
4088
|
description: statusSug.description,
|
|
3918
|
-
reasoning:
|
|
4089
|
+
reasoning:
|
|
4090
|
+
'Filter by status to focus on specific record states',
|
|
3919
4091
|
});
|
|
3920
4092
|
});
|
|
3921
4093
|
}
|
|
3922
|
-
} else if (
|
|
4094
|
+
} else if (
|
|
4095
|
+
[
|
|
4096
|
+
'created_at',
|
|
4097
|
+
'updated_at',
|
|
4098
|
+
'date',
|
|
4099
|
+
'due_date',
|
|
4100
|
+
'deadline',
|
|
4101
|
+
].includes(columnLower)
|
|
4102
|
+
) {
|
|
3923
4103
|
// Date-based filters
|
|
3924
4104
|
const dateFilters = [
|
|
3925
4105
|
{
|
|
3926
4106
|
days: 7,
|
|
3927
4107
|
title: 'Last 7 days',
|
|
3928
|
-
description: `Show ${tableDisplayName.toLowerCase()} from the last week
|
|
4108
|
+
description: `Show ${tableDisplayName.toLowerCase()} from the last week`,
|
|
3929
4109
|
},
|
|
3930
4110
|
{
|
|
3931
4111
|
days: 30,
|
|
3932
|
-
title: 'Last 30 days',
|
|
3933
|
-
description: `Show ${tableDisplayName.toLowerCase()} from the last month
|
|
4112
|
+
title: 'Last 30 days',
|
|
4113
|
+
description: `Show ${tableDisplayName.toLowerCase()} from the last month`,
|
|
3934
4114
|
},
|
|
3935
4115
|
{
|
|
3936
4116
|
days: 90,
|
|
3937
4117
|
title: 'Last 3 months',
|
|
3938
|
-
description: `Show ${tableDisplayName.toLowerCase()} from the last quarter
|
|
3939
|
-
}
|
|
4118
|
+
description: `Show ${tableDisplayName.toLowerCase()} from the last quarter`,
|
|
4119
|
+
},
|
|
3940
4120
|
];
|
|
3941
|
-
|
|
3942
|
-
dateFilters.forEach(dateFilter => {
|
|
4121
|
+
|
|
4122
|
+
dateFilters.forEach((dateFilter) => {
|
|
3943
4123
|
suggestions.filters.push({
|
|
3944
4124
|
type: 'filter',
|
|
3945
4125
|
table: selectedTable,
|
|
3946
4126
|
column: columnName,
|
|
3947
4127
|
operator: '>=',
|
|
3948
|
-
value: moment()
|
|
3949
|
-
|
|
4128
|
+
value: moment()
|
|
4129
|
+
.subtract(dateFilter.days, 'days')
|
|
4130
|
+
.format('YYYY-MM-DD'),
|
|
4131
|
+
title: `${dateFilter.title} (${formatName(
|
|
4132
|
+
columnName
|
|
4133
|
+
)})`,
|
|
3950
4134
|
description: dateFilter.description,
|
|
3951
|
-
reasoning: 'Focus on recent activity and data'
|
|
4135
|
+
reasoning: 'Focus on recent activity and data',
|
|
3952
4136
|
});
|
|
3953
4137
|
});
|
|
3954
|
-
} else if (
|
|
4138
|
+
} else if (
|
|
4139
|
+
['priority', 'importance', 'urgency'].includes(
|
|
4140
|
+
columnLower
|
|
4141
|
+
)
|
|
4142
|
+
) {
|
|
3955
4143
|
// Priority-based filters
|
|
3956
4144
|
const priorityFilters = [
|
|
3957
4145
|
{ value: 'high', title: 'High priority only' },
|
|
3958
|
-
{
|
|
3959
|
-
|
|
4146
|
+
{
|
|
4147
|
+
value: 'medium',
|
|
4148
|
+
title: 'Medium priority and above',
|
|
4149
|
+
},
|
|
4150
|
+
{ value: 'urgent', title: 'Urgent items only' },
|
|
3960
4151
|
];
|
|
3961
|
-
|
|
3962
|
-
priorityFilters.forEach(priorityFilter => {
|
|
4152
|
+
|
|
4153
|
+
priorityFilters.forEach((priorityFilter) => {
|
|
3963
4154
|
suggestions.filters.push({
|
|
3964
4155
|
type: 'filter',
|
|
3965
4156
|
table: selectedTable,
|
|
@@ -3967,11 +4158,16 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
3967
4158
|
operator: '=',
|
|
3968
4159
|
value: priorityFilter.value,
|
|
3969
4160
|
title: priorityFilter.title,
|
|
3970
|
-
description: `Filter to show ${
|
|
3971
|
-
|
|
4161
|
+
description: `Filter to show ${
|
|
4162
|
+
priorityFilter.value
|
|
4163
|
+
} priority ${tableDisplayName.toLowerCase()}`,
|
|
4164
|
+
reasoning:
|
|
4165
|
+
'Focus on high-priority items that need attention',
|
|
3972
4166
|
});
|
|
3973
4167
|
});
|
|
3974
|
-
} else if (
|
|
4168
|
+
} else if (
|
|
4169
|
+
['type', 'category', 'kind'].includes(columnLower)
|
|
4170
|
+
) {
|
|
3975
4171
|
// Type/Category filters - generic suggestions
|
|
3976
4172
|
suggestions.filters.push({
|
|
3977
4173
|
type: 'filter',
|
|
@@ -3979,11 +4175,17 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
3979
4175
|
column: columnName,
|
|
3980
4176
|
operator: '!=',
|
|
3981
4177
|
value: '',
|
|
3982
|
-
title: `Show records with ${formatName(
|
|
3983
|
-
|
|
3984
|
-
|
|
4178
|
+
title: `Show records with ${formatName(
|
|
4179
|
+
columnName
|
|
4180
|
+
)}`,
|
|
4181
|
+
description: `Filter out records without a ${formatName(
|
|
4182
|
+
columnName
|
|
4183
|
+
)} specified`,
|
|
4184
|
+
reasoning: 'Focus on categorized records',
|
|
3985
4185
|
});
|
|
3986
|
-
} else if (
|
|
4186
|
+
} else if (
|
|
4187
|
+
['email', 'phone', 'mobile'].includes(columnLower)
|
|
4188
|
+
) {
|
|
3987
4189
|
// Contact information filters
|
|
3988
4190
|
suggestions.filters.push({
|
|
3989
4191
|
type: 'filter',
|
|
@@ -3992,10 +4194,20 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
3992
4194
|
operator: '!=',
|
|
3993
4195
|
value: '',
|
|
3994
4196
|
title: `Has ${formatName(columnName)}`,
|
|
3995
|
-
description: `Show only records with ${formatName(
|
|
3996
|
-
|
|
4197
|
+
description: `Show only records with ${formatName(
|
|
4198
|
+
columnName
|
|
4199
|
+
)} information`,
|
|
4200
|
+
reasoning:
|
|
4201
|
+
'Focus on records with contact information',
|
|
3997
4202
|
});
|
|
3998
|
-
} else if (
|
|
4203
|
+
} else if (
|
|
4204
|
+
[
|
|
4205
|
+
'client_id',
|
|
4206
|
+
'user_id',
|
|
4207
|
+
'owner_id',
|
|
4208
|
+
'assigned_to',
|
|
4209
|
+
].includes(columnLower)
|
|
4210
|
+
) {
|
|
3999
4211
|
// Relationship filters
|
|
4000
4212
|
suggestions.filters.push({
|
|
4001
4213
|
type: 'filter',
|
|
@@ -4003,47 +4215,68 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
4003
4215
|
column: columnName,
|
|
4004
4216
|
operator: '!=',
|
|
4005
4217
|
value: '',
|
|
4006
|
-
title: `Has ${formatName(
|
|
4007
|
-
|
|
4008
|
-
|
|
4218
|
+
title: `Has ${formatName(
|
|
4219
|
+
columnName.replace('_id', '')
|
|
4220
|
+
)}`,
|
|
4221
|
+
description: `Show only records with an assigned ${formatName(
|
|
4222
|
+
columnName.replace('_id', '')
|
|
4223
|
+
)}`,
|
|
4224
|
+
reasoning:
|
|
4225
|
+
'Focus on records with assigned relationships',
|
|
4009
4226
|
});
|
|
4010
4227
|
}
|
|
4011
4228
|
}
|
|
4012
4229
|
});
|
|
4013
4230
|
|
|
4014
4231
|
// Add filter suggestions from joined tables
|
|
4015
|
-
joins.forEach(join => {
|
|
4232
|
+
joins.forEach((join) => {
|
|
4016
4233
|
if (join.targetTable && join.availableColumns) {
|
|
4017
|
-
const joinTableDisplay = getTableDisplayName(
|
|
4018
|
-
|
|
4234
|
+
const joinTableDisplay = getTableDisplayName(
|
|
4235
|
+
join.targetTable,
|
|
4236
|
+
definition
|
|
4237
|
+
);
|
|
4238
|
+
|
|
4019
4239
|
// Add filter suggestions from joined tables
|
|
4020
|
-
['status', 'active', 'type', 'priority'].forEach(
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4240
|
+
['status', 'active', 'type', 'priority'].forEach(
|
|
4241
|
+
(columnName) => {
|
|
4242
|
+
if (columnExists(columnName, join.targetTable)) {
|
|
4243
|
+
suggestions.filters.push({
|
|
4244
|
+
type: 'filter',
|
|
4245
|
+
table: join.targetTable,
|
|
4246
|
+
column: columnName,
|
|
4247
|
+
operator: '=',
|
|
4248
|
+
value:
|
|
4249
|
+
columnName === 'active'
|
|
4250
|
+
? '1'
|
|
4251
|
+
: 'active',
|
|
4252
|
+
title: `Filter by ${joinTableDisplay} ${formatName(
|
|
4253
|
+
columnName
|
|
4254
|
+
)}`,
|
|
4255
|
+
description: `Show records based on ${formatName(
|
|
4256
|
+
columnName
|
|
4257
|
+
)} from connected ${joinTableDisplay.toLowerCase()}`,
|
|
4258
|
+
reasoning: `Use related ${joinTableDisplay.toLowerCase()} data for filtering`,
|
|
4259
|
+
});
|
|
4260
|
+
}
|
|
4032
4261
|
}
|
|
4033
|
-
|
|
4034
|
-
|
|
4262
|
+
);
|
|
4263
|
+
|
|
4035
4264
|
// Add date filters for joined tables
|
|
4036
|
-
['created_at', 'updated_at'].forEach(columnName => {
|
|
4265
|
+
['created_at', 'updated_at'].forEach((columnName) => {
|
|
4037
4266
|
if (columnExists(columnName, join.targetTable)) {
|
|
4038
4267
|
suggestions.filters.push({
|
|
4039
4268
|
type: 'filter',
|
|
4040
4269
|
table: join.targetTable,
|
|
4041
4270
|
column: columnName,
|
|
4042
4271
|
operator: '>=',
|
|
4043
|
-
value: moment()
|
|
4272
|
+
value: moment()
|
|
4273
|
+
.subtract(30, 'days')
|
|
4274
|
+
.format('YYYY-MM-DD'),
|
|
4044
4275
|
title: `Recent ${joinTableDisplay} (Last 30 days)`,
|
|
4045
|
-
description: `Filter by ${joinTableDisplay.toLowerCase()} ${formatName(
|
|
4046
|
-
|
|
4276
|
+
description: `Filter by ${joinTableDisplay.toLowerCase()} ${formatName(
|
|
4277
|
+
columnName
|
|
4278
|
+
)} in the last 30 days`,
|
|
4279
|
+
reasoning: `Focus on recent ${joinTableDisplay.toLowerCase()} activity`,
|
|
4047
4280
|
});
|
|
4048
4281
|
}
|
|
4049
4282
|
});
|
|
@@ -4051,49 +4284,56 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
4051
4284
|
});
|
|
4052
4285
|
|
|
4053
4286
|
// Combine all suggestions into a single array (prioritize filters)
|
|
4054
|
-
const allSuggestions = [
|
|
4287
|
+
const allSuggestions = [
|
|
4288
|
+
...suggestions.sorting.slice(0, 3),
|
|
4289
|
+
...suggestions.filters.slice(0, 5),
|
|
4290
|
+
];
|
|
4055
4291
|
|
|
4056
4292
|
// Check which suggestions are already applied
|
|
4057
|
-
const enhancedSuggestions = allSuggestions.map(suggestion => {
|
|
4293
|
+
const enhancedSuggestions = allSuggestions.map((suggestion) => {
|
|
4058
4294
|
let alreadyApplied = false;
|
|
4059
|
-
|
|
4295
|
+
|
|
4060
4296
|
if (suggestion.type === 'sorting') {
|
|
4061
4297
|
// Check if this sort criterion already exists
|
|
4062
4298
|
alreadyApplied = sortCriteria.some(
|
|
4063
|
-
criteria =>
|
|
4064
|
-
|
|
4065
|
-
|
|
4299
|
+
(criteria) =>
|
|
4300
|
+
criteria.table === suggestion.table &&
|
|
4301
|
+
criteria.column === suggestion.column &&
|
|
4302
|
+
criteria.direction === suggestion.direction
|
|
4066
4303
|
);
|
|
4067
4304
|
} else if (suggestion.type === 'filter') {
|
|
4068
4305
|
// Check if this filter already exists in any group
|
|
4069
|
-
alreadyApplied =
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4306
|
+
alreadyApplied =
|
|
4307
|
+
filterCriteria.groups &&
|
|
4308
|
+
filterCriteria.groups.some((group) =>
|
|
4309
|
+
group.filters.some(
|
|
4310
|
+
(filter) =>
|
|
4311
|
+
filter.table === suggestion.table &&
|
|
4312
|
+
filter.column === suggestion.column &&
|
|
4313
|
+
filter.operator === suggestion.operator &&
|
|
4314
|
+
filter.value === suggestion.value
|
|
4315
|
+
)
|
|
4316
|
+
);
|
|
4077
4317
|
}
|
|
4078
|
-
|
|
4318
|
+
|
|
4079
4319
|
return {
|
|
4080
4320
|
...suggestion,
|
|
4081
|
-
alreadyApplied
|
|
4321
|
+
alreadyApplied,
|
|
4082
4322
|
};
|
|
4083
4323
|
});
|
|
4084
4324
|
|
|
4085
4325
|
// Return prioritized suggestions (max 6-8 for clean interface)
|
|
4086
4326
|
return enhancedSuggestions.slice(0, 8);
|
|
4087
4327
|
};
|
|
4088
|
-
|
|
4328
|
+
|
|
4089
4329
|
const smartSuggestions = getSmartSuggestions();
|
|
4090
|
-
|
|
4330
|
+
|
|
4091
4331
|
// Debug logging to check if suggestions are generated
|
|
4092
4332
|
console.log('Smart suggestions debug:', {
|
|
4093
4333
|
selectedTable,
|
|
4094
4334
|
tableColumns: tableColumns?.length,
|
|
4095
4335
|
smartSuggestions: smartSuggestions?.length,
|
|
4096
|
-
suggestions: smartSuggestions
|
|
4336
|
+
suggestions: smartSuggestions,
|
|
4097
4337
|
});
|
|
4098
4338
|
|
|
4099
4339
|
return (
|
|
@@ -4107,42 +4347,74 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
4107
4347
|
Smart Suggestions
|
|
4108
4348
|
</h3>
|
|
4109
4349
|
<span className={styles.suggestionsCount}>
|
|
4110
|
-
{smartSuggestions.length} suggestion
|
|
4350
|
+
{smartSuggestions.length} suggestion
|
|
4351
|
+
{smartSuggestions.length !== 1 ? 's' : ''}
|
|
4111
4352
|
</span>
|
|
4112
4353
|
</div>
|
|
4113
|
-
|
|
4354
|
+
|
|
4114
4355
|
<div className={styles.smartSuggestionsContainer}>
|
|
4115
4356
|
<p className={styles.suggestionsIntro}>
|
|
4116
|
-
Based on your selected data source "
|
|
4117
|
-
|
|
4357
|
+
Based on your selected data source "
|
|
4358
|
+
{formatName(selectedTable)}" and relationships,
|
|
4359
|
+
here are some recommended sorting and filtering
|
|
4360
|
+
options:
|
|
4118
4361
|
</p>
|
|
4119
|
-
|
|
4362
|
+
|
|
4120
4363
|
<div className={styles.suggestionsGrid}>
|
|
4121
4364
|
{smartSuggestions.map((suggestion, index) => (
|
|
4122
|
-
<div
|
|
4123
|
-
|
|
4124
|
-
|
|
4365
|
+
<div
|
|
4366
|
+
key={index}
|
|
4367
|
+
className={styles.suggestionCard}
|
|
4368
|
+
>
|
|
4369
|
+
<div
|
|
4370
|
+
className={styles.suggestionHeader}
|
|
4371
|
+
>
|
|
4372
|
+
<span
|
|
4373
|
+
className={
|
|
4374
|
+
styles.suggestionTitle
|
|
4375
|
+
}
|
|
4376
|
+
>
|
|
4125
4377
|
{suggestion.title}
|
|
4126
4378
|
</span>
|
|
4127
|
-
<span
|
|
4379
|
+
<span
|
|
4380
|
+
className={
|
|
4381
|
+
styles.suggestionType
|
|
4382
|
+
}
|
|
4383
|
+
>
|
|
4128
4384
|
{suggestion.type}
|
|
4129
4385
|
</span>
|
|
4130
4386
|
</div>
|
|
4131
|
-
|
|
4132
|
-
<p
|
|
4387
|
+
|
|
4388
|
+
<p
|
|
4389
|
+
className={
|
|
4390
|
+
styles.suggestionDescription
|
|
4391
|
+
}
|
|
4392
|
+
>
|
|
4133
4393
|
{suggestion.description}
|
|
4134
4394
|
</p>
|
|
4135
|
-
|
|
4136
|
-
<div
|
|
4395
|
+
|
|
4396
|
+
<div
|
|
4397
|
+
className={styles.suggestionActions}
|
|
4398
|
+
>
|
|
4137
4399
|
<button
|
|
4138
4400
|
className={`${styles.btn} ${styles.btnPrimary}`}
|
|
4139
|
-
onClick={() =>
|
|
4140
|
-
|
|
4401
|
+
onClick={() =>
|
|
4402
|
+
applySuggestion(suggestion)
|
|
4403
|
+
}
|
|
4404
|
+
disabled={
|
|
4405
|
+
suggestion.alreadyApplied
|
|
4406
|
+
}
|
|
4141
4407
|
>
|
|
4142
|
-
{suggestion.alreadyApplied
|
|
4408
|
+
{suggestion.alreadyApplied
|
|
4409
|
+
? 'Applied'
|
|
4410
|
+
: 'Apply'}
|
|
4143
4411
|
</button>
|
|
4144
4412
|
{suggestion.reasoning && (
|
|
4145
|
-
<span
|
|
4413
|
+
<span
|
|
4414
|
+
className={
|
|
4415
|
+
styles.suggestionReasoning
|
|
4416
|
+
}
|
|
4417
|
+
>
|
|
4146
4418
|
{suggestion.reasoning}
|
|
4147
4419
|
</span>
|
|
4148
4420
|
)}
|
|
@@ -4158,7 +4430,9 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
4158
4430
|
<div className={styles.reportSection}>
|
|
4159
4431
|
<div
|
|
4160
4432
|
className={styles.sectionHeader}
|
|
4161
|
-
onClick={() =>
|
|
4433
|
+
onClick={() =>
|
|
4434
|
+
setShowSortingSection(!showSortingSection)
|
|
4435
|
+
}
|
|
4162
4436
|
title={
|
|
4163
4437
|
showSortingSection
|
|
4164
4438
|
? 'Hide sorting options'
|
|
@@ -4200,95 +4474,143 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
4200
4474
|
key={index}
|
|
4201
4475
|
className={styles.sortingItem}
|
|
4202
4476
|
>
|
|
4203
|
-
<div
|
|
4477
|
+
<div
|
|
4478
|
+
className={styles.sortingHeader}
|
|
4479
|
+
>
|
|
4204
4480
|
<h4>
|
|
4205
|
-
Sort #{index + 1} -
|
|
4206
|
-
|
|
4481
|
+
Sort #{index + 1} -{' '}
|
|
4482
|
+
{criterion.table &&
|
|
4483
|
+
criterion.column
|
|
4484
|
+
? `${formatName(
|
|
4485
|
+
criterion.table
|
|
4486
|
+
)}.${formatName(
|
|
4487
|
+
criterion.column
|
|
4488
|
+
)}`
|
|
4207
4489
|
: 'Configure column'}
|
|
4208
4490
|
</h4>
|
|
4209
4491
|
<button
|
|
4210
|
-
className={
|
|
4211
|
-
|
|
4492
|
+
className={
|
|
4493
|
+
styles.removeSortBtn
|
|
4494
|
+
}
|
|
4495
|
+
onClick={() =>
|
|
4496
|
+
handleRemoveSortCriterion(
|
|
4497
|
+
index
|
|
4498
|
+
)
|
|
4499
|
+
}
|
|
4212
4500
|
title="Remove Sort Criterion"
|
|
4213
4501
|
>
|
|
4214
4502
|
×
|
|
4215
4503
|
</button>
|
|
4216
4504
|
</div>
|
|
4217
4505
|
|
|
4218
|
-
<div
|
|
4219
|
-
|
|
4506
|
+
<div
|
|
4507
|
+
className={
|
|
4508
|
+
styles.sortingControls
|
|
4509
|
+
}
|
|
4510
|
+
>
|
|
4511
|
+
<div
|
|
4512
|
+
className={
|
|
4513
|
+
styles.sortingField
|
|
4514
|
+
}
|
|
4515
|
+
>
|
|
4220
4516
|
<label>Column:</label>
|
|
4221
4517
|
<select
|
|
4222
|
-
className={
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4518
|
+
className={
|
|
4519
|
+
styles.sortingSelect
|
|
4520
|
+
}
|
|
4521
|
+
value={
|
|
4522
|
+
criterion.table &&
|
|
4523
|
+
criterion.column
|
|
4524
|
+
? `${criterion.table}.${criterion.column}`
|
|
4525
|
+
: ''
|
|
4526
|
+
}
|
|
4527
|
+
onChange={(e) => {
|
|
4528
|
+
if (
|
|
4529
|
+
e.target.value
|
|
4530
|
+
) {
|
|
4531
|
+
const dotIndex =
|
|
4532
|
+
e.target.value.indexOf(
|
|
4533
|
+
'.'
|
|
4534
|
+
);
|
|
4535
|
+
if (
|
|
4536
|
+
dotIndex !==
|
|
4537
|
+
-1
|
|
4538
|
+
) {
|
|
4539
|
+
const table =
|
|
4540
|
+
e.target.value.substring(
|
|
4541
|
+
0,
|
|
4542
|
+
dotIndex
|
|
4543
|
+
);
|
|
4544
|
+
const column =
|
|
4545
|
+
e.target.value.substring(
|
|
4546
|
+
dotIndex +
|
|
4547
|
+
1
|
|
4548
|
+
);
|
|
4549
|
+
|
|
4550
|
+
handleSortCriterionChange(
|
|
4551
|
+
index,
|
|
4552
|
+
'column',
|
|
4553
|
+
''
|
|
4554
|
+
);
|
|
4555
|
+
handleSortCriterionChange(
|
|
4556
|
+
index,
|
|
4557
|
+
'table',
|
|
4558
|
+
table
|
|
4559
|
+
);
|
|
4560
|
+
handleSortCriterionChange(
|
|
4561
|
+
index,
|
|
4562
|
+
'column',
|
|
4563
|
+
column
|
|
4564
|
+
);
|
|
4565
|
+
}
|
|
4566
|
+
}
|
|
4567
|
+
}}
|
|
4568
|
+
>
|
|
4569
|
+
<option value="">
|
|
4570
|
+
Select Column
|
|
4571
|
+
</option>
|
|
4572
|
+
{/* Main table columns */}
|
|
4573
|
+
<optgroup
|
|
4574
|
+
label={`${formatName(
|
|
4575
|
+
selectedTable
|
|
4576
|
+
)} Columns`}
|
|
4577
|
+
>
|
|
4578
|
+
{tableColumns.map(
|
|
4579
|
+
(column) => (
|
|
4580
|
+
<option
|
|
4581
|
+
key={`${selectedTable}.${column.name}`}
|
|
4582
|
+
value={`${selectedTable}.${column.name}`}
|
|
4583
|
+
>
|
|
4584
|
+
{formatName(
|
|
4585
|
+
column.name
|
|
4586
|
+
)}
|
|
4278
4587
|
</option>
|
|
4279
|
-
|
|
4588
|
+
)
|
|
4589
|
+
)}
|
|
4590
|
+
</optgroup>
|
|
4591
|
+
|
|
4592
|
+
{/* Joined tables columns */}
|
|
4593
|
+
{joins.map(
|
|
4594
|
+
(join, joinIndex) =>
|
|
4595
|
+
join.targetTable &&
|
|
4596
|
+
join.availableColumns &&
|
|
4597
|
+
join
|
|
4598
|
+
.availableColumns
|
|
4599
|
+
.length >
|
|
4600
|
+
0 ? (
|
|
4280
4601
|
<optgroup
|
|
4602
|
+
key={`join-${joinIndex}`}
|
|
4281
4603
|
label={`${formatName(
|
|
4282
|
-
|
|
4604
|
+
join.targetTable
|
|
4283
4605
|
)} Columns`}
|
|
4284
4606
|
>
|
|
4285
|
-
{
|
|
4607
|
+
{join.availableColumns.map(
|
|
4286
4608
|
(
|
|
4287
4609
|
column
|
|
4288
4610
|
) => (
|
|
4289
4611
|
<option
|
|
4290
|
-
key={`${
|
|
4291
|
-
value={`${
|
|
4612
|
+
key={`${join.targetTable}.${column.name}`}
|
|
4613
|
+
value={`${join.targetTable}.${column.name}`}
|
|
4292
4614
|
>
|
|
4293
4615
|
{formatName(
|
|
4294
4616
|
column.name
|
|
@@ -4297,70 +4619,38 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
4297
4619
|
)
|
|
4298
4620
|
)}
|
|
4299
4621
|
</optgroup>
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
(
|
|
4304
|
-
join,
|
|
4305
|
-
joinIndex
|
|
4306
|
-
) =>
|
|
4307
|
-
join.targetTable &&
|
|
4308
|
-
join.availableColumns &&
|
|
4309
|
-
join
|
|
4310
|
-
.availableColumns
|
|
4311
|
-
.length >
|
|
4312
|
-
0 ? (
|
|
4313
|
-
<optgroup
|
|
4314
|
-
key={`join-${joinIndex}`}
|
|
4315
|
-
label={`${formatName(
|
|
4316
|
-
join.targetTable
|
|
4317
|
-
)} Columns`}
|
|
4318
|
-
>
|
|
4319
|
-
{join.availableColumns.map(
|
|
4320
|
-
(
|
|
4321
|
-
column
|
|
4322
|
-
) => (
|
|
4323
|
-
<option
|
|
4324
|
-
key={`${join.targetTable}.${column.name}`}
|
|
4325
|
-
value={`${join.targetTable}.${column.name}`}
|
|
4326
|
-
>
|
|
4327
|
-
{formatName(
|
|
4328
|
-
column.name
|
|
4329
|
-
)}
|
|
4330
|
-
</option>
|
|
4331
|
-
)
|
|
4332
|
-
)}
|
|
4333
|
-
</optgroup>
|
|
4334
|
-
) : null
|
|
4335
|
-
)}
|
|
4336
|
-
</select>
|
|
4622
|
+
) : null
|
|
4623
|
+
)}
|
|
4624
|
+
</select>
|
|
4337
4625
|
</div>
|
|
4338
4626
|
|
|
4339
|
-
<div
|
|
4627
|
+
<div
|
|
4628
|
+
className={
|
|
4629
|
+
styles.sortingField
|
|
4630
|
+
}
|
|
4631
|
+
>
|
|
4340
4632
|
<label>Direction:</label>
|
|
4341
4633
|
<select
|
|
4342
|
-
className={
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
Descending
|
|
4363
|
-
</option>
|
|
4634
|
+
className={
|
|
4635
|
+
styles.sortingSelect
|
|
4636
|
+
}
|
|
4637
|
+
value={
|
|
4638
|
+
criterion.direction
|
|
4639
|
+
}
|
|
4640
|
+
onChange={(e) =>
|
|
4641
|
+
handleSortCriterionChange(
|
|
4642
|
+
index,
|
|
4643
|
+
'direction',
|
|
4644
|
+
e.target.value
|
|
4645
|
+
)
|
|
4646
|
+
}
|
|
4647
|
+
>
|
|
4648
|
+
<option value="ASC">
|
|
4649
|
+
Ascending
|
|
4650
|
+
</option>
|
|
4651
|
+
<option value="DESC">
|
|
4652
|
+
Descending
|
|
4653
|
+
</option>
|
|
4364
4654
|
</select>
|
|
4365
4655
|
</div>
|
|
4366
4656
|
</div>
|
|
@@ -4876,9 +5166,15 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
4876
5166
|
console.log('Selected Columns:', selectedColumns);
|
|
4877
5167
|
console.log('Joins:', joins);
|
|
4878
5168
|
console.log('Filter Criteria:', filterCriteria);
|
|
4879
|
-
console.log(
|
|
5169
|
+
console.log(
|
|
5170
|
+
'Flattened Filters:',
|
|
5171
|
+
flattenFilterCriteria(filterCriteria)
|
|
5172
|
+
);
|
|
4880
5173
|
console.log('Sort Criteria:', sortCriteria);
|
|
4881
|
-
console.log(
|
|
5174
|
+
console.log(
|
|
5175
|
+
'🚀 Final Payload:',
|
|
5176
|
+
JSON.stringify(exportData, null, 2)
|
|
5177
|
+
);
|
|
4882
5178
|
console.groupEnd();
|
|
4883
5179
|
|
|
4884
5180
|
// Generate filename with timestamp
|
|
@@ -4895,12 +5191,15 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
4895
5191
|
const result = await Download(exportUrl, 'POST', exportData);
|
|
4896
5192
|
|
|
4897
5193
|
console.log('✅ Download component returned result:', result);
|
|
4898
|
-
|
|
5194
|
+
|
|
4899
5195
|
if (result && result.data) {
|
|
4900
5196
|
console.log('✅ Result has data, triggering download');
|
|
4901
5197
|
console.log('📄 Result data type:', typeof result.data);
|
|
4902
|
-
console.log(
|
|
4903
|
-
|
|
5198
|
+
console.log(
|
|
5199
|
+
'📄 Result data instanceof Blob:',
|
|
5200
|
+
result.data instanceof Blob
|
|
5201
|
+
);
|
|
5202
|
+
|
|
4904
5203
|
// Use saveAs to trigger download
|
|
4905
5204
|
saveAs(result.data, fileName);
|
|
4906
5205
|
toast.success(`Report exported as ${format.toUpperCase()}`);
|
|
@@ -4918,45 +5217,68 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
4918
5217
|
console.log('Error name:', error.name);
|
|
4919
5218
|
console.log('Error message:', error.message);
|
|
4920
5219
|
console.log('Error stack:', error.stack);
|
|
4921
|
-
|
|
5220
|
+
|
|
4922
5221
|
let errorMessage = `Failed to export report as ${format.toUpperCase()}`;
|
|
4923
|
-
|
|
5222
|
+
|
|
4924
5223
|
if (error.response) {
|
|
4925
5224
|
console.log('📡 Error response received');
|
|
4926
5225
|
console.log('Response status:', error.response.status);
|
|
4927
5226
|
console.log('Response statusText:', error.response.statusText);
|
|
4928
5227
|
console.log('Response headers:', error.response.headers);
|
|
4929
5228
|
console.log('Response data type:', typeof error.response.data);
|
|
4930
|
-
console.log(
|
|
5229
|
+
console.log(
|
|
5230
|
+
'Response data instanceof Blob:',
|
|
5231
|
+
error.response.data instanceof Blob
|
|
5232
|
+
);
|
|
4931
5233
|
console.log('Raw response data:', error.response.data);
|
|
4932
|
-
|
|
5234
|
+
|
|
4933
5235
|
if (error.response.data instanceof Blob) {
|
|
4934
5236
|
// If error response is a blob, try to read it as text
|
|
4935
5237
|
try {
|
|
4936
|
-
console.log(
|
|
5238
|
+
console.log(
|
|
5239
|
+
'🔍 Attempting to parse blob error response'
|
|
5240
|
+
);
|
|
4937
5241
|
const errorText = await error.response.data.text();
|
|
4938
5242
|
console.log('📄 Blob content as text:', errorText);
|
|
4939
|
-
|
|
5243
|
+
|
|
4940
5244
|
try {
|
|
4941
5245
|
const errorData = JSON.parse(errorText);
|
|
4942
|
-
console.log(
|
|
4943
|
-
|
|
5246
|
+
console.log(
|
|
5247
|
+
'📄 Parsed JSON error data:',
|
|
5248
|
+
errorData
|
|
5249
|
+
);
|
|
5250
|
+
errorMessage =
|
|
5251
|
+
errorData.message ||
|
|
5252
|
+
errorData.error ||
|
|
5253
|
+
errorMessage;
|
|
4944
5254
|
} catch (jsonError) {
|
|
4945
|
-
console.error(
|
|
5255
|
+
console.error(
|
|
5256
|
+
'❌ Failed to parse JSON from blob:',
|
|
5257
|
+
jsonError
|
|
5258
|
+
);
|
|
4946
5259
|
errorMessage = errorText || errorMessage;
|
|
4947
5260
|
}
|
|
4948
5261
|
} catch (parseError) {
|
|
4949
5262
|
// If parsing fails, use default message
|
|
4950
|
-
console.error(
|
|
5263
|
+
console.error(
|
|
5264
|
+
'❌ Could not parse error response blob:',
|
|
5265
|
+
parseError
|
|
5266
|
+
);
|
|
4951
5267
|
}
|
|
4952
5268
|
} else if (error.response.data && error.response.data.message) {
|
|
4953
|
-
console.log(
|
|
5269
|
+
console.log(
|
|
5270
|
+
'📄 Using error.response.data.message:',
|
|
5271
|
+
error.response.data.message
|
|
5272
|
+
);
|
|
4954
5273
|
errorMessage = error.response.data.message;
|
|
4955
5274
|
} else if (error.response.data && error.response.data.error) {
|
|
4956
|
-
console.log(
|
|
5275
|
+
console.log(
|
|
5276
|
+
'📄 Using error.response.data.error:',
|
|
5277
|
+
error.response.data.error
|
|
5278
|
+
);
|
|
4957
5279
|
errorMessage = error.response.data.error;
|
|
4958
5280
|
}
|
|
4959
|
-
|
|
5281
|
+
|
|
4960
5282
|
// Handle specific 500 errors with empty response (likely memory issues)
|
|
4961
5283
|
if (error.response.status === 500 && !error.response.data) {
|
|
4962
5284
|
errorMessage = `Server error during ${format.toUpperCase()} generation. This may be due to:
|
|
@@ -4967,10 +5289,10 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
4967
5289
|
} else {
|
|
4968
5290
|
console.log('❌ No error.response available');
|
|
4969
5291
|
}
|
|
4970
|
-
|
|
5292
|
+
|
|
4971
5293
|
console.log('🔔 Final error message:', errorMessage);
|
|
4972
5294
|
console.groupEnd();
|
|
4973
|
-
|
|
5295
|
+
|
|
4974
5296
|
toast.error(errorMessage);
|
|
4975
5297
|
console.error('Export error:', error);
|
|
4976
5298
|
}
|
|
@@ -4988,15 +5310,13 @@ const GenericReportImproved = ({ setting = {}, definition = null }) => {
|
|
|
4988
5310
|
// Determine if this is a save or save-as operation
|
|
4989
5311
|
const isExistingReport = loadedReportId && !saveAs;
|
|
4990
5312
|
const dialogTitle = isExistingReport ? 'Update Report' : 'Save Report';
|
|
4991
|
-
const confirmButtonText = isExistingReport
|
|
4992
|
-
? 'Update'
|
|
4993
|
-
: 'Save Report';
|
|
5313
|
+
const confirmButtonText = isExistingReport ? 'Update' : 'Save Report';
|
|
4994
5314
|
|
|
4995
5315
|
const result = await Swal.fire({
|
|
4996
5316
|
title: dialogTitle,
|
|
4997
5317
|
customClass: {
|
|
4998
5318
|
confirmButton: 'swal-button-fixed-width',
|
|
4999
|
-
cancelButton: 'swal-button-fixed-width'
|
|
5319
|
+
cancelButton: 'swal-button-fixed-width',
|
|
5000
5320
|
},
|
|
5001
5321
|
html: `
|
|
5002
5322
|
<div style="text-align: left;">
|