@visns-studio/visns-components 5.10.11 → 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.
Files changed (55) hide show
  1. package/README.md +85 -0
  2. package/package.json +7 -7
  3. package/src/components/cms/DataGrid.jsx +8 -8
  4. package/src/components/cms/DropZone.jsx +1 -1
  5. package/src/components/cms/Form.jsx +1 -1
  6. package/src/components/cms/Gallery.jsx +1 -1
  7. package/src/components/cms/sorting/Item.jsx +1 -1
  8. package/src/components/crm/Autocomplete.jsx +3 -3
  9. package/src/components/crm/Breadcrumb.jsx +4 -4
  10. package/src/components/crm/BusinessCardOcr.jsx +681 -95
  11. package/src/components/crm/DataGrid.jsx +34 -32
  12. package/src/components/crm/Field.jsx +12 -12
  13. package/src/components/crm/Form.jsx +2 -2
  14. package/src/components/crm/Navigation.jsx +11 -11
  15. package/src/components/crm/Notification.jsx +2 -2
  16. package/src/components/crm/QuickAction.jsx +3 -3
  17. package/src/components/crm/SectionHeader.jsx +1 -1
  18. package/src/components/crm/SwitchAccount.jsx +4 -4
  19. package/src/components/crm/auth/ClientLogin.jsx +2 -2
  20. package/src/components/crm/auth/ClientOTPVerify.jsx +2 -2
  21. package/src/components/crm/auth/Login.jsx +3 -3
  22. package/src/components/crm/auth/Reset.jsx +2 -2
  23. package/src/components/crm/auth/TwoFactorAuth.jsx +2 -2
  24. package/src/components/crm/columns/ColumnRenderers.jsx +320 -259
  25. package/src/components/crm/controls/DataGridSearch.jsx +5 -5
  26. package/src/components/crm/generic/AlternativeActionModal.jsx +100 -0
  27. package/src/components/crm/generic/ContactSelectorModal.jsx +423 -0
  28. package/src/components/crm/generic/DatePickerDialog.jsx +302 -0
  29. package/src/components/crm/generic/DateRangeSelectorModal.jsx +181 -0
  30. package/src/components/crm/generic/GenericClientPortal.jsx +1 -1
  31. package/src/components/crm/generic/GenericDashboard.jsx +4 -4
  32. package/src/components/crm/generic/GenericDetail.jsx +6 -6
  33. package/src/components/crm/generic/GenericDynamic.jsx +3 -3
  34. package/src/components/crm/generic/GenericEditableTable.jsx +4 -4
  35. package/src/components/crm/generic/GenericFormBuilder.jsx +6 -6
  36. package/src/components/crm/generic/GenericGrid.jsx +2888 -0
  37. package/src/components/crm/generic/GenericIndex.jsx +5 -1255
  38. package/src/components/crm/generic/GenericReport.jsx +966 -646
  39. package/src/components/crm/generic/GenericReportForm.jsx +194 -0
  40. package/src/components/crm/generic/NotificationList.jsx +1 -1
  41. package/src/components/crm/generic/ReasonCollectorModal.jsx +194 -0
  42. package/src/components/crm/generic/SortableQuoteItems.jsx +3 -3
  43. package/src/components/crm/generic/shared/formatters.js +231 -0
  44. package/src/components/crm/generic/shared/gridUtils.js +194 -0
  45. package/src/components/crm/generic/styles/AlternativeActionModal.css +127 -0
  46. package/src/components/crm/generic/styles/ContactSelectorModal.css +367 -0
  47. package/src/components/crm/generic/styles/DatePickerDialog.css +84 -0
  48. package/src/components/crm/generic/styles/DateRangeSelectorModal.css +115 -0
  49. package/src/components/crm/generic/styles/GenericIndex.module.scss +382 -0
  50. package/src/components/crm/generic/styles/GenericReport.module.scss +96 -0
  51. package/src/components/crm/generic/styles/ReasonCollectorModal.css +217 -0
  52. package/src/components/crm/modals/GalleryModal.jsx +2 -2
  53. package/src/components/crm/sorting/Item.jsx +3 -3
  54. package/src/components/crm/styles/BusinessCardOcr.module.scss +197 -4
  55. package/src/index.js +4 -0
@@ -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 'akar-icons';
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
- // Show guided help popup
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: '🎯 Quick Start Guide',
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: 14px; color: #4b5563;">
1130
- <div style="margin-bottom: 20px;">
1131
- <h4 style="color: #1f2937; margin-bottom: 10px;">Choose Your Path:</h4>
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
- <div style="background: #f0f9ff; border: 1px solid #3b82f6; border-radius: 8px; padding: 12px; margin-bottom: 12px;">
1134
- <h5 style="color: #1e40af; margin: 0 0 8px 0;">🚀 Quick Start (Recommended)</h5>
1135
- <p style="margin: 0;">Use a saved report to get started immediately. Perfect for common reports like client lists or recent invoices.</p>
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
- <div style="background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 8px; padding: 12px;">
1139
- <h5 style="color: #374151; margin: 0 0 8px 0;">✨ Custom Report</h5>
1140
- <p style="margin: 0;">Build your own report from scratch with our step-by-step wizard. We'll guide you through each step!</p>
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
- <div style="background: #fef3c7; border: 1px solid #f59e0b; border-radius: 8px; padding: 12px;">
1145
- <p style="margin: 0; color: #92400e;">
1146
- <strong>💡 Tip:</strong> Follow the wizard steps to create professional reports quickly and easily.
1147
- </p>
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: 500,
1152
- confirmButtonText: "Let's Get Started!",
1153
- confirmButtonColor: '#2563eb',
1154
- showCancelButton: true,
1155
- cancelButtonText: 'Skip Tour',
1156
- cancelButtonColor: '#6b7280',
1157
- }).then((result) => {
1158
- if (result.isConfirmed) {
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(`Invalid columns data format for ${type} table:`, result);
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) => criteria.table === suggestion.table && criteria.column === suggestion.column
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 (!updatedCriteria.groups || updatedCriteria.groups.length === 0) {
1918
- updatedCriteria.groups = [{
1919
- operator: 'AND',
1920
- filters: []
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 source:
3104
+ We found these possible connections to other data
3105
+ source:
3121
3106
  </p>
3122
3107
  <div className={styles.detectionsGrid}>
3123
- {tableSuggestions.map((suggestion, index) => (
3124
- <div key={index} className={styles.detectionCard}>
3125
- <div className={styles.detectionInfo}>
3126
- <div className={styles.detectionTitle}>
3127
- <LinkChain size={16} />
3128
- <span>
3129
- Connect to{' '}
3130
- {getSmartRelationshipName(
3131
- suggestion.targetTable,
3132
- definition
3133
- )}
3134
- </span>
3135
- <div
3136
- className={styles.confidenceRating}
3137
- >
3138
- {'⭐'.repeat(
3139
- Math.floor(
3140
- suggestion.confidence / 20
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
- <p className={styles.detectionDescription}>
3146
- {suggestion.description ||
3147
- `Link ${getTableDisplayName(
3148
- selectedTable,
3149
- definition
3150
- )} with ${getTableDisplayName(
3151
- suggestion.targetTable,
3152
- definition
3153
- )}`}
3154
- </p>
3155
- </div>
3156
- <button
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
- ? styles.connected
3164
- : ''
3165
- }`}
3166
- onClick={() => addJoin(suggestion)}
3167
- disabled={joins.some(
3168
- (j) =>
3169
- j.targetTable ===
3170
- suggestion.targetTable
3171
- )}
3172
- title={
3173
- joins.some(
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
- ? 'Already connected'
3179
- : `Connect to ${getSmartRelationshipName(
3180
- suggestion.targetTable,
3181
- definition
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 relationships require understanding of database table structures and foreign key relationships. Only create manual relationships if you understand how database tables connect.
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(manualJoinColumns.source) && manualJoinColumns.source.map((col) => (
3318
- <option
3319
- key={col.name}
3320
- value={col.name}
3321
- >
3322
- {formatName(col.name)}
3323
- </option>
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(manualJoinColumns.target) && manualJoinColumns.target.map((col) => (
3377
- <option
3378
- key={col.name}
3379
- value={col.name}
3380
- >
3381
- {formatName(col.name)}
3382
- </option>
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(selectedTable, definition);
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: { priority: ['name', 'created_at', 'updated_at', 'email'], filters: ['active', 'status', 'created_at', 'email', 'role'] },
3783
- clients: { priority: ['name', 'created_at', 'updated_at', 'email'], filters: ['active', 'status', 'created_at', 'email', 'phone', 'type'] },
3784
- contacts: { priority: ['name', 'email', 'created_at'], filters: ['active', 'client_id', 'email', 'phone', 'created_at'] },
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: { priority: ['created_at', 'updated_at', 'name', 'status'], filters: ['status', 'source', 'priority', 'created_at', 'assigned_to', 'email'] },
3788
- projects: { priority: ['created_at', 'deadline', 'name', 'status'], filters: ['status', 'priority', 'created_at', 'due_date', 'client_id', 'assigned_to'] },
3789
- tasks: { priority: ['created_at', 'deadline', 'priority'], filters: ['status', 'priority', 'assigned_to', 'created_at', 'due_date'] },
3790
- surveys: { priority: ['created_at', 'updated_at', 'status'], filters: ['status', 'type', 'created_at', 'active'] },
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: { priority: ['name', 'address', 'created_at'], filters: ['active', 'state', 'client_id', 'type', 'status'] },
3794
- locations: { priority: ['name', 'address', 'created_at'], filters: ['active', 'state', 'type', 'status'] },
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: { priority: ['name', 'id'], filters: ['active', 'type'] },
3799
- actions: { priority: ['name', 'priority'], filters: ['active', 'priority', 'type'] },
3800
- tags: { priority: ['name', 'created_at'], filters: ['active', 'type', 'category'] },
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: { priority: ['created_at', 'name', 'type'], filters: ['type', 'status', 'created_at', 'category', 'owner_id'] },
3804
- notes: { priority: ['created_at', 'updated_at'], filters: ['created_at', 'user_id', 'type', 'priority'] },
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: { priority: ['created_at', 'subject'], filters: ['status', 'created_at', 'from_email', 'to_email', 'type'] },
3808
- calls: { priority: ['created_at', 'duration'], filters: ['status', 'created_at', 'type', 'outcome'] },
3809
- messages: { priority: ['created_at', 'subject'], filters: ['status', 'created_at', 'type', 'priority'] },
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: { priority: ['created_at', 'status', 'total'], filters: ['status', 'created_at', 'client_id', 'type'] },
3813
- invoices: { priority: ['created_at', 'due_date', 'status'], filters: ['status', 'created_at', 'due_date', 'client_id'] },
3814
- bookings: { priority: ['date', 'created_at', 'status'], filters: ['status', 'date', 'created_at', 'client_id', 'type'] },
3815
- facilities: { priority: ['name', 'type', 'rate'], filters: ['type', 'active', 'status', 'category'] },
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: { priority: ['created_at', 'amount', 'status'], filters: ['status', 'created_at', 'method', 'type'] },
3819
- transactions: { priority: ['created_at', 'amount', 'type'], filters: ['type', 'status', 'created_at', 'category'] },
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(col => col.name.toLowerCase() === columnName.toLowerCase());
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 join && join.availableColumns && join.availableColumns.some(col => col.name.toLowerCase() === columnName.toLowerCase());
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 = ['created_at', 'updated_at', 'date', 'deadline', 'due_date'].includes(columnName.toLowerCase());
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)} (${direction === 'desc' ? 'Newest First' : 'A-Z'})`,
3850
- description: `Order results by ${formatName(columnName)} in ${direction === 'desc' ? 'descending' : 'ascending'} order`,
3851
- reasoning: isDateColumn ? 'Show most recent records first' : 'Alphabetical ordering for easy browsing'
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: 'Focus on currently relevant records'
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 (['leads', 'projects', 'tasks'].includes(selectedTable.toLowerCase())) {
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 (['orders', 'bookings', 'invoices'].includes(selectedTable.toLowerCase())) {
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: 'Filter by status to focus on specific record states'
4089
+ reasoning:
4090
+ 'Filter by status to focus on specific record states',
3919
4091
  });
3920
4092
  });
3921
4093
  }
3922
- } else if (['created_at', 'updated_at', 'date', 'due_date', 'deadline'].includes(columnLower)) {
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().subtract(dateFilter.days, 'days').format('YYYY-MM-DD'),
3949
- title: `${dateFilter.title} (${formatName(columnName)})`,
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 (['priority', 'importance', 'urgency'].includes(columnLower)) {
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
- { value: 'medium', title: 'Medium priority and above' },
3959
- { value: 'urgent', title: 'Urgent items only' }
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 ${priorityFilter.value} priority ${tableDisplayName.toLowerCase()}`,
3971
- reasoning: 'Focus on high-priority items that need attention'
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 (['type', 'category', 'kind'].includes(columnLower)) {
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(columnName)}`,
3983
- description: `Filter out records without a ${formatName(columnName)} specified`,
3984
- reasoning: 'Focus on categorized records'
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 (['email', 'phone', 'mobile'].includes(columnLower)) {
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(columnName)} information`,
3996
- reasoning: 'Focus on records with contact information'
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 (['client_id', 'user_id', 'owner_id', 'assigned_to'].includes(columnLower)) {
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(columnName.replace('_id', ''))}`,
4007
- description: `Show only records with an assigned ${formatName(columnName.replace('_id', ''))}`,
4008
- reasoning: 'Focus on records with assigned relationships'
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(join.targetTable, definition);
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(columnName => {
4021
- if (columnExists(columnName, join.targetTable)) {
4022
- suggestions.filters.push({
4023
- type: 'filter',
4024
- table: join.targetTable,
4025
- column: columnName,
4026
- operator: '=',
4027
- value: columnName === 'active' ? '1' : 'active',
4028
- title: `Filter by ${joinTableDisplay} ${formatName(columnName)}`,
4029
- description: `Show records based on ${formatName(columnName)} from connected ${joinTableDisplay.toLowerCase()}`,
4030
- reasoning: `Use related ${joinTableDisplay.toLowerCase()} data for filtering`
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().subtract(30, 'days').format('YYYY-MM-DD'),
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(columnName)} in the last 30 days`,
4046
- reasoning: `Focus on recent ${joinTableDisplay.toLowerCase()} activity`
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 = [...suggestions.sorting.slice(0, 3), ...suggestions.filters.slice(0, 5)];
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 => criteria.table === suggestion.table &&
4064
- criteria.column === suggestion.column &&
4065
- criteria.direction === suggestion.direction
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 = filterCriteria.groups && filterCriteria.groups.some(group =>
4070
- group.filters.some(filter =>
4071
- filter.table === suggestion.table &&
4072
- filter.column === suggestion.column &&
4073
- filter.operator === suggestion.operator &&
4074
- filter.value === suggestion.value
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{smartSuggestions.length !== 1 ? 's' : ''}
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 "{formatName(selectedTable)}" and relationships,
4117
- here are some recommended sorting and filtering options:
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 key={index} className={styles.suggestionCard}>
4123
- <div className={styles.suggestionHeader}>
4124
- <span className={styles.suggestionTitle}>
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 className={styles.suggestionType}>
4379
+ <span
4380
+ className={
4381
+ styles.suggestionType
4382
+ }
4383
+ >
4128
4384
  {suggestion.type}
4129
4385
  </span>
4130
4386
  </div>
4131
-
4132
- <p className={styles.suggestionDescription}>
4387
+
4388
+ <p
4389
+ className={
4390
+ styles.suggestionDescription
4391
+ }
4392
+ >
4133
4393
  {suggestion.description}
4134
4394
  </p>
4135
-
4136
- <div className={styles.suggestionActions}>
4395
+
4396
+ <div
4397
+ className={styles.suggestionActions}
4398
+ >
4137
4399
  <button
4138
4400
  className={`${styles.btn} ${styles.btnPrimary}`}
4139
- onClick={() => applySuggestion(suggestion)}
4140
- disabled={suggestion.alreadyApplied}
4401
+ onClick={() =>
4402
+ applySuggestion(suggestion)
4403
+ }
4404
+ disabled={
4405
+ suggestion.alreadyApplied
4406
+ }
4141
4407
  >
4142
- {suggestion.alreadyApplied ? 'Applied' : 'Apply'}
4408
+ {suggestion.alreadyApplied
4409
+ ? 'Applied'
4410
+ : 'Apply'}
4143
4411
  </button>
4144
4412
  {suggestion.reasoning && (
4145
- <span className={styles.suggestionReasoning}>
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={() => setShowSortingSection(!showSortingSection)}
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 className={styles.sortingHeader}>
4477
+ <div
4478
+ className={styles.sortingHeader}
4479
+ >
4204
4480
  <h4>
4205
- Sort #{index + 1} - {criterion.table && criterion.column
4206
- ? `${formatName(criterion.table)}.${formatName(criterion.column)}`
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={styles.removeSortBtn}
4211
- onClick={() => handleRemoveSortCriterion(index)}
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 className={styles.sortingControls}>
4219
- <div className={styles.sortingField}>
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={styles.sortingSelect}
4223
- value={
4224
- criterion.table &&
4225
- criterion.column
4226
- ? `${criterion.table}.${criterion.column}`
4227
- : ''
4228
- }
4229
- onChange={(
4230
- e
4231
- ) => {
4232
- if (
4233
- e
4234
- .target
4235
- .value
4236
- ) {
4237
- const dotIndex =
4238
- e.target.value.indexOf(
4239
- '.'
4240
- );
4241
- if (
4242
- dotIndex !==
4243
- -1
4244
- ) {
4245
- const table =
4246
- e.target.value.substring(
4247
- 0,
4248
- dotIndex
4249
- );
4250
- const column =
4251
- e.target.value.substring(
4252
- dotIndex +
4253
- 1
4254
- );
4255
-
4256
- handleSortCriterionChange(
4257
- index,
4258
- 'column',
4259
- ''
4260
- );
4261
- handleSortCriterionChange(
4262
- index,
4263
- 'table',
4264
- table
4265
- );
4266
- handleSortCriterionChange(
4267
- index,
4268
- 'column',
4269
- column
4270
- );
4271
- }
4272
- }
4273
- }}
4274
- >
4275
- <option value="">
4276
- Select
4277
- Column
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
- {/* Main table columns */}
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
- selectedTable
4604
+ join.targetTable
4283
4605
  )} Columns`}
4284
4606
  >
4285
- {tableColumns.map(
4607
+ {join.availableColumns.map(
4286
4608
  (
4287
4609
  column
4288
4610
  ) => (
4289
4611
  <option
4290
- key={`${selectedTable}.${column.name}`}
4291
- value={`${selectedTable}.${column.name}`}
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
- {/* Joined tables columns */}
4302
- {joins.map(
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 className={styles.sortingField}>
4627
+ <div
4628
+ className={
4629
+ styles.sortingField
4630
+ }
4631
+ >
4340
4632
  <label>Direction:</label>
4341
4633
  <select
4342
- className={styles.sortingSelect}
4343
- value={
4344
- criterion.direction
4345
- }
4346
- onChange={(
4347
- e
4348
- ) =>
4349
- handleSortCriterionChange(
4350
- index,
4351
- 'direction',
4352
- e
4353
- .target
4354
- .value
4355
- )
4356
- }
4357
- >
4358
- <option value="ASC">
4359
- Ascending
4360
- </option>
4361
- <option value="DESC">
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('Flattened Filters:', flattenFilterCriteria(filterCriteria));
5169
+ console.log(
5170
+ 'Flattened Filters:',
5171
+ flattenFilterCriteria(filterCriteria)
5172
+ );
4880
5173
  console.log('Sort Criteria:', sortCriteria);
4881
- console.log('🚀 Final Payload:', JSON.stringify(exportData, null, 2));
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('📄 Result data instanceof Blob:', result.data instanceof Blob);
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('Response data instanceof Blob:', error.response.data instanceof Blob);
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('🔍 Attempting to parse blob error response');
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('📄 Parsed JSON error data:', errorData);
4943
- errorMessage = errorData.message || errorData.error || errorMessage;
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('❌ Failed to parse JSON from blob:', jsonError);
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('❌ Could not parse error response blob:', parseError);
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('📄 Using error.response.data.message:', error.response.data.message);
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('📄 Using error.response.data.error:', error.response.data.error);
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;">