@visns-studio/visns-components 5.5.1 → 5.5.3

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.
@@ -2,11 +2,13 @@ import React, { useState, useEffect } from 'react';
2
2
  import { toast } from 'react-toastify';
3
3
  import moment from 'moment';
4
4
  import ReactDataGrid from '@inovua/reactdatagrid-community';
5
- import { CircleX } from 'akar-icons';
5
+ import { CircleX, Question } from 'akar-icons';
6
6
  import CustomFetch from '../Fetch';
7
7
  import Download from '../Download';
8
8
  import { saveAs } from 'file-saver';
9
+ import Swal from 'sweetalert2';
9
10
  import styles from './styles/GenericReport.module.scss';
11
+ import './styles/SweetAlertCustom.css';
10
12
 
11
13
  // Utility function to format names from camelCase or snake_case to Title Case with spaces
12
14
  const formatName = (name) => {
@@ -179,6 +181,142 @@ const GenericReport = ({ setting = {} }) => {
179
181
  // Extract settings with defaults
180
182
  const { tableUrl, columnUrl } = setting;
181
183
 
184
+ // Help functions for showing popups
185
+ const showReportBuilderHelp = () => {
186
+ Swal.fire({
187
+ title: 'How to Use the Report Builder',
188
+ html: `
189
+ <div style="text-align: left; margin-bottom: 20px; font-size: 14px; color: #4b5563;">
190
+ <h4 style="font-size: 16px; color: #1f2937; margin-bottom: 10px; font-weight: 600;">Getting Started:</h4>
191
+ <ol style="padding-left: 18px; margin-bottom: 14px; line-height: 1.4;">
192
+ <li>Select a table from the left panel</li>
193
+ <li>Choose columns you want to include in your report</li>
194
+ <li>Optionally, add relationships to connect data from different tables</li>
195
+ <li>Click "Execute Query" to preview your report</li>
196
+ <li>Save or export your report when ready</li>
197
+ </ol>
198
+
199
+ <h4 style="font-size: 16px; color: #1f2937; margin-bottom: 10px; margin-top: 16px; font-weight: 600;">Understanding Relationships:</h4>
200
+ <p style="margin-bottom: 8px; line-height: 1.4;">Tables in a database are connected through relationships. For example:</p>
201
+ <ul style="padding-left: 18px; margin-bottom: 14px; line-height: 1.4; font-size: 14px !important;">
202
+ <li style="font-size: 14px !important; margin-bottom: 4px;"><span style="font-weight: 500;">Clients → Sites</span>: Each client can have multiple sites</li>
203
+ <li style="font-size: 14px !important; margin-bottom: 4px;"><span style="font-weight: 500;">Sites → Contacts</span>: Each site can have multiple contacts</li>
204
+ </ul>
205
+ <p style="margin-bottom: 8px; line-height: 1.4;">Adding these relationships lets you combine data from multiple tables.</p>
206
+
207
+ <h4 style="font-size: 16px; color: #1f2937; margin-bottom: 10px; margin-top: 16px; font-weight: 600;">Suggested Relationships:</h4>
208
+ <p style="margin-bottom: 8px; line-height: 1.4;">The system automatically suggests relationships based on the database structure:</p>
209
+ <ul style="padding-left: 18px; line-height: 1.4; font-size: 14px !important; margin-bottom: 14px;">
210
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><span style="color: #f59e0b;">⭐⭐⭐</span> = High confidence (very likely correct)</li>
211
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><span style="color: #f59e0b;">⭐⭐</span> = Medium confidence</li>
212
+ <li style="font-size: 14px !important;"><span style="color: #f59e0b;">⭐</span> = Low confidence (might need verification)</li>
213
+ </ul>
214
+
215
+ <h4 style="font-size: 16px; color: #1f2937; margin-bottom: 10px; margin-top: 16px; font-weight: 600;">Using Sorting and Filtering:</h4>
216
+ <p style="margin-bottom: 8px; line-height: 1.4;">Customize your reports with powerful sorting and filtering options:</p>
217
+ <ul style="padding-left: 18px; line-height: 1.4; font-size: 14px !important; margin-bottom: 14px;">
218
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><strong>Sorting</strong>: Click the "Sorting" header to expand the section, then add sort criteria to order your results</li>
219
+ <li style="margin-bottom: 4px; font-size: 14px !important;">You can sort by multiple columns (e.g., sort by last name, then by first name)</li>
220
+ <li style="margin-bottom: 4px; font-size: 14px !important;">Choose between ascending (A-Z, 0-9) or descending (Z-A, 9-0) order</li>
221
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><strong>Filtering</strong>: Click the "Filtering" header to expand the section, then add filter criteria</li>
222
+ <li style="margin-bottom: 4px; font-size: 14px !important;">Use operators like equals, contains, greater than, etc. to narrow down results</li>
223
+ <li style="margin-bottom: 4px; font-size: 14px !important;">Combine multiple filters for precise data selection (all filters are combined with AND logic)</li>
224
+ <li style="font-size: 14px !important;">Sorting and filtering settings are saved with your report for future use</li>
225
+ </ul>
226
+
227
+ <h4 style="font-size: 16px; color: #1f2937; margin-bottom: 10px; margin-top: 16px; font-weight: 600;">Tips for Effective Reports:</h4>
228
+ <p style="margin-bottom: 8px; line-height: 1.4;">Follow these best practices for creating useful reports:</p>
229
+ <ul style="padding-left: 18px; line-height: 1.4; font-size: 14px !important; margin-bottom: 14px;">
230
+ <li style="margin-bottom: 4px; font-size: 14px !important;">Start with the main table that contains your primary data</li>
231
+ <li style="margin-bottom: 4px; font-size: 14px !important;">Select only the columns you need to avoid cluttered reports</li>
232
+ <li style="margin-bottom: 4px; font-size: 14px !important;">Use joins to bring in related data rather than creating separate reports</li>
233
+ <li style="margin-bottom: 4px; font-size: 14px !important;">Consider adding date fields to enable time-based filtering</li>
234
+ <li style="font-size: 14px !important;">Save frequently used reports for quick access</li>
235
+ </ul>
236
+
237
+
238
+ </div>
239
+ `,
240
+ width: 550,
241
+ confirmButtonText: 'Got it!',
242
+ confirmButtonColor: '#2563eb',
243
+ customClass: {
244
+ title: 'swal-title',
245
+ htmlContainer: 'swal-html-container',
246
+ confirmButton: 'swal-confirm-button',
247
+ },
248
+ });
249
+ };
250
+
251
+ const showDataTypesHelp = () => {
252
+ Swal.fire({
253
+ title: 'Understanding Data Types',
254
+ html: `
255
+ <div style="text-align: left; margin-bottom: 20px; font-size: 14px; color: #4b5563;">
256
+ <p style="margin-bottom: 12px; line-height: 1.4;">When selecting columns, you'll see different data types. Here's what they mean:</p>
257
+
258
+ <h4 style="font-size: 16px; color: #1f2937; margin-bottom: 10px; font-weight: 600;">String Data Types:</h4>
259
+ <ul style="padding-left: 18px; margin-bottom: 14px; line-height: 1.4; font-size: 14px !important;">
260
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">CHAR</span>: Fixed-length text (codes, abbreviations)</li>
261
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">VARCHAR</span>: Variable-length text (names, descriptions)</li>
262
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">TINYTEXT</span>: Very small text (up to 255 characters)</li>
263
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">TEXT</span>: Standard text (up to 65,535 characters)</li>
264
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">MEDIUMTEXT</span>: Medium-sized text (up to 16.7 million characters)</li>
265
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">LONGTEXT</span>: Large text (up to 4.2 billion characters)</li>
266
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">ENUM</span>: Field with predefined options</li>
267
+ <li style="font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">SET</span>: Field that can contain multiple predefined options</li>
268
+ </ul>
269
+
270
+ <h4 style="font-size: 16px; color: #1f2937; margin-bottom: 10px; margin-top: 16px; font-weight: 600;">Numeric Data Types:</h4>
271
+ <ul style="padding-left: 18px; margin-bottom: 14px; line-height: 1.4; font-size: 14px !important;">
272
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">TINYINT</span>: Very small integers (-128 to 127)</li>
273
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">SMALLINT</span>: Small integers (-32,768 to 32,767)</li>
274
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">MEDIUMINT</span>: Medium-sized integers (-8.3M to 8.3M)</li>
275
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">INT/INTEGER</span>: Standard integers (-2.1B to 2.1B)</li>
276
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">BIGINT</span>: Large integers (very large range)</li>
277
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">DECIMAL/NUMERIC</span>: Fixed-point numbers (precise values)</li>
278
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">FLOAT</span>: Single-precision floating point</li>
279
+ <li style="font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">DOUBLE</span>: Double-precision floating point</li>
280
+ </ul>
281
+
282
+ <h4 style="font-size: 16px; color: #1f2937; margin-bottom: 10px; margin-top: 16px; font-weight: 600;">Date and Time Types:</h4>
283
+ <ul style="padding-left: 18px; margin-bottom: 14px; line-height: 1.4; font-size: 14px !important;">
284
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">DATE</span>: Date values (YYYY-MM-DD)</li>
285
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">TIME</span>: Time values (HH:MM:SS)</li>
286
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">DATETIME</span>: Date and time (YYYY-MM-DD HH:MM:SS)</li>
287
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">TIMESTAMP</span>: Date and time with timezone awareness</li>
288
+ <li style="font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">YEAR</span>: Year values (YYYY)</li>
289
+ </ul>
290
+
291
+ <h4 style="font-size: 16px; color: #1f2937; margin-bottom: 10px; margin-top: 16px; font-weight: 600;">Binary and Specialized Types:</h4>
292
+ <ul style="padding-left: 18px; margin-bottom: 14px; line-height: 1.4; font-size: 14px !important;">
293
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">BINARY/VARBINARY</span>: Binary strings</li>
294
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">TINYBLOB</span>: Very small binary objects (up to 255 bytes)</li>
295
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">BLOB</span>: Binary objects (up to 65,535 bytes)</li>
296
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">MEDIUMBLOB</span>: Medium-sized binary objects</li>
297
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">LONGBLOB</span>: Large binary objects</li>
298
+ <li style="margin-bottom: 4px; font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">JSON</span>: Structured data in JSON format</li>
299
+ <li style="font-size: 14px !important;"><span style="font-weight: 500; color: #2563eb;">BOOLEAN/BOOL</span>: True/false values (stored as TINYINT)</li>
300
+ </ul>
301
+
302
+ <h4 style="font-size: 16px; color: #1f2937; margin-bottom: 10px; margin-top: 16px; font-weight: 600;">Primary Keys:</h4>
303
+ <p style="line-height: 1.4; margin-bottom: 8px;">Columns marked as "Primary Key" uniquely identify each record in a table. These are often used to create relationships between tables.</p>
304
+
305
+ <h4 style="font-size: 16px; color: #1f2937; margin-bottom: 10px; margin-top: 16px; font-weight: 600;">Foreign Keys:</h4>
306
+ <p style="line-height: 1.4;">Foreign keys connect tables by referencing the primary key of another table. They enable the creation of relationships between tables.</p>
307
+ </div>
308
+ `,
309
+ width: 650,
310
+ confirmButtonText: 'Got it!',
311
+ confirmButtonColor: '#2563eb',
312
+ customClass: {
313
+ title: 'swal-title',
314
+ htmlContainer: 'swal-html-container',
315
+ confirmButton: 'swal-confirm-button',
316
+ },
317
+ });
318
+ };
319
+
182
320
  // No mock data - component will rely on API responses
183
321
 
184
322
  // State for database schema
@@ -204,6 +342,17 @@ const GenericReport = ({ setting = {} }) => {
204
342
  const [suggestedJoins, setSuggestedJoins] = useState({});
205
343
  const [isLoadingSuggestedJoins, setIsLoadingSuggestedJoins] = useState({});
206
344
 
345
+ // State for toggling visibility of sections
346
+ const [showSuggestedRelationships, setShowSuggestedRelationships] =
347
+ useState({});
348
+ const [showJoinTables, setShowJoinTables] = useState(false);
349
+ const [showSortingSection, setShowSortingSection] = useState(false);
350
+ const [showFilteringSection, setShowFilteringSection] = useState(false);
351
+
352
+ // State for sorting and filtering
353
+ const [sortCriteria, setSortCriteria] = useState([]);
354
+ const [filterCriteria, setFilterCriteria] = useState([]);
355
+
207
356
  // State for report execution
208
357
  const [totalResults, setTotalResults] = useState(0);
209
358
  const [executedSql, setExecutedSql] = useState('');
@@ -306,6 +455,11 @@ const GenericReport = ({ setting = {} }) => {
306
455
 
307
456
  // Fetch suggested joins for a table
308
457
  const fetchSuggestedJoins = async (tableName, tableKey) => {
458
+ // Initialize visibility state for this table's relationships (default: hidden)
459
+ setShowSuggestedRelationships((prev) => ({
460
+ ...prev,
461
+ [tableKey]: false,
462
+ }));
309
463
  // Update loading state for this specific table
310
464
  setIsLoadingSuggestedJoins((prev) => ({
311
465
  ...prev,
@@ -430,6 +584,11 @@ const GenericReport = ({ setting = {} }) => {
430
584
  setPreviewData([]);
431
585
  setExecutedSql('');
432
586
  setTotalResults(0);
587
+ setShowJoinTables(false);
588
+ setShowSortingSection(false);
589
+ setShowFilteringSection(false);
590
+ setSortCriteria([]);
591
+ setFilterCriteria([]);
433
592
  };
434
593
 
435
594
  // Handle table selection
@@ -533,6 +692,8 @@ const GenericReport = ({ setting = {} }) => {
533
692
  };
534
693
 
535
694
  setJoins([...joins, newJoin]);
695
+ // Show the join tables section when adding a new join
696
+ setShowJoinTables(true);
536
697
  };
537
698
 
538
699
  // Add a suggested join
@@ -561,6 +722,9 @@ const GenericReport = ({ setting = {} }) => {
561
722
  setJoins([...joins, firstJoin, secondJoin]);
562
723
  toast.success('Added many-to-many relationship with pivot table');
563
724
 
725
+ // Show the join tables section when adding a suggested join
726
+ setShowJoinTables(true);
727
+
564
728
  // Fetch columns for both target tables
565
729
  fetchJoinTableColumns(suggestedJoin.targetTable, joins.length);
566
730
  if (suggestedJoin.secondJoin.targetTable) {
@@ -583,6 +747,9 @@ const GenericReport = ({ setting = {} }) => {
583
747
  setJoins([...joins, newJoin]);
584
748
  toast.success('Added suggested join');
585
749
 
750
+ // Show the join tables section when adding a suggested join
751
+ setShowJoinTables(true);
752
+
586
753
  // Fetch columns for the target table
587
754
  if (suggestedJoin.targetTable) {
588
755
  fetchJoinTableColumns(suggestedJoin.targetTable, joins.length);
@@ -681,6 +848,89 @@ const GenericReport = ({ setting = {} }) => {
681
848
  setJoins(updatedJoins);
682
849
  };
683
850
 
851
+ // Add a new sort criterion
852
+ const handleAddSortCriterion = () => {
853
+ // Default to the first selected column if available
854
+ const defaultColumn =
855
+ selectedColumns.length > 0
856
+ ? {
857
+ table: selectedColumns[0].table,
858
+ column: selectedColumns[0].column,
859
+ displayName: selectedColumns[0].displayName,
860
+ }
861
+ : null;
862
+
863
+ const newSortCriterion = {
864
+ table: defaultColumn?.table || '',
865
+ column: defaultColumn?.column || '',
866
+ direction: 'ASC',
867
+ };
868
+
869
+ setSortCriteria([...sortCriteria, newSortCriterion]);
870
+ setShowSortingSection(true);
871
+ };
872
+
873
+ // Update a sort criterion
874
+ const handleSortCriterionChange = (index, field, value) => {
875
+ const updatedSortCriteria = [...sortCriteria];
876
+ updatedSortCriteria[index][field] = value;
877
+ setSortCriteria(updatedSortCriteria);
878
+ };
879
+
880
+ // Remove a sort criterion
881
+ const handleRemoveSortCriterion = (index) => {
882
+ const updatedSortCriteria = [...sortCriteria];
883
+ updatedSortCriteria.splice(index, 1);
884
+ setSortCriteria(updatedSortCriteria);
885
+
886
+ // Hide the section if no sort criteria remain
887
+ if (updatedSortCriteria.length === 0) {
888
+ setShowSortingSection(false);
889
+ }
890
+ };
891
+
892
+ // Add a new filter criterion
893
+ const handleAddFilterCriterion = () => {
894
+ // Default to the first selected column if available
895
+ const defaultColumn =
896
+ selectedColumns.length > 0
897
+ ? {
898
+ table: selectedColumns[0].table,
899
+ column: selectedColumns[0].column,
900
+ displayName: selectedColumns[0].displayName,
901
+ }
902
+ : null;
903
+
904
+ const newFilterCriterion = {
905
+ table: defaultColumn?.table || '',
906
+ column: defaultColumn?.column || '',
907
+ operator: '=',
908
+ value: '',
909
+ };
910
+
911
+ setFilterCriteria([...filterCriteria, newFilterCriterion]);
912
+ setShowFilteringSection(true);
913
+ };
914
+
915
+ // Update a filter criterion
916
+ const handleFilterCriterionChange = (index, field, value) => {
917
+ const updatedFilterCriteria = [...filterCriteria];
918
+ updatedFilterCriteria[index][field] = value;
919
+ setFilterCriteria(updatedFilterCriteria);
920
+ };
921
+
922
+ // Remove a filter criterion
923
+ const handleRemoveFilterCriterion = (index) => {
924
+ const updatedFilterCriteria = [...filterCriteria];
925
+ updatedFilterCriteria.splice(index, 1);
926
+ setFilterCriteria(updatedFilterCriteria);
927
+
928
+ // Hide the section if no filter criteria remain
929
+ if (updatedFilterCriteria.length === 0) {
930
+ setShowFilteringSection(false);
931
+ }
932
+ };
933
+
684
934
  // Delete a saved report
685
935
  const handleDeleteReport = async (reportId) => {
686
936
  if (!reportId) return;
@@ -868,6 +1118,28 @@ const GenericReport = ({ setting = {} }) => {
868
1118
  // Set joins
869
1119
  if (detail.joins && Array.isArray(detail.joins)) {
870
1120
  setJoins(detail.joins);
1121
+ // Show join tables section if there are joins
1122
+ if (detail.joins.length > 0) {
1123
+ setShowJoinTables(true);
1124
+ }
1125
+ }
1126
+
1127
+ // Set sorting criteria
1128
+ if (detail.sorting && Array.isArray(detail.sorting)) {
1129
+ setSortCriteria(detail.sorting);
1130
+ // Show sorting section if there are sort criteria
1131
+ if (detail.sorting.length > 0) {
1132
+ setShowSortingSection(true);
1133
+ }
1134
+ }
1135
+
1136
+ // Set filtering criteria
1137
+ if (detail.filters && Array.isArray(detail.filters)) {
1138
+ setFilterCriteria(detail.filters);
1139
+ // Show filtering section if there are filter criteria
1140
+ if (detail.filters.length > 0) {
1141
+ setShowFilteringSection(true);
1142
+ }
871
1143
  }
872
1144
 
873
1145
  toast.success(
@@ -934,6 +1206,28 @@ const GenericReport = ({ setting = {} }) => {
934
1206
  // Set joins
935
1207
  if (detail.joins && Array.isArray(detail.joins)) {
936
1208
  setJoins(detail.joins);
1209
+ // Show join tables section if there are joins
1210
+ if (detail.joins.length > 0) {
1211
+ setShowJoinTables(true);
1212
+ }
1213
+ }
1214
+
1215
+ // Set sorting criteria
1216
+ if (detail.sorting && Array.isArray(detail.sorting)) {
1217
+ setSortCriteria(detail.sorting);
1218
+ // Show sorting section if there are sort criteria
1219
+ if (detail.sorting.length > 0) {
1220
+ setShowSortingSection(true);
1221
+ }
1222
+ }
1223
+
1224
+ // Set filtering criteria
1225
+ if (detail.filters && Array.isArray(detail.filters)) {
1226
+ setFilterCriteria(detail.filters);
1227
+ // Show filtering section if there are filter criteria
1228
+ if (detail.filters.length > 0) {
1229
+ setShowFilteringSection(true);
1230
+ }
937
1231
  }
938
1232
 
939
1233
  // Wait a moment for state to update
@@ -974,8 +1268,8 @@ const GenericReport = ({ setting = {} }) => {
974
1268
  sourceColumn: join.sourceColumn,
975
1269
  targetColumn: join.targetColumn,
976
1270
  })),
977
- filters: [],
978
- sorting: [],
1271
+ filters: filterCriteria,
1272
+ sorting: sortCriteria,
979
1273
  };
980
1274
 
981
1275
  // Execute query
@@ -1151,8 +1445,8 @@ const GenericReport = ({ setting = {} }) => {
1151
1445
  sourceColumn: join.sourceColumn,
1152
1446
  targetColumn: join.targetColumn,
1153
1447
  })),
1154
- filters: [],
1155
- sorting: [],
1448
+ filters: filterCriteria,
1449
+ sorting: sortCriteria,
1156
1450
  };
1157
1451
 
1158
1452
  // Determine if we're updating or creating a new report
@@ -1204,7 +1498,27 @@ const GenericReport = ({ setting = {} }) => {
1204
1498
  <>
1205
1499
  <div className={styles.grid__full}>
1206
1500
  <div className={styles.crmtitle}>
1207
- <h1>Report Builder</h1>
1501
+ <div className={styles.titleContainer}>
1502
+ <h1>Report Builder</h1>
1503
+ <div className={styles.helpButtons}>
1504
+ <button
1505
+ className={styles.helpButton}
1506
+ onClick={showReportBuilderHelp}
1507
+ title="How to use the Report Builder"
1508
+ >
1509
+ <Question size={18} />
1510
+ <span>How to use</span>
1511
+ </button>
1512
+ <button
1513
+ className={styles.helpButton}
1514
+ onClick={showDataTypesHelp}
1515
+ title="Understanding data types"
1516
+ >
1517
+ <Question size={18} />
1518
+ <span>Data types</span>
1519
+ </button>
1520
+ </div>
1521
+ </div>
1208
1522
  <div className={styles.titleInfo}>
1209
1523
  <span>
1210
1524
  Create custom reports by selecting tables, columns,
@@ -1564,213 +1878,177 @@ const GenericReport = ({ setting = {} }) => {
1564
1878
  )}
1565
1879
  </div>
1566
1880
 
1567
- {joins.length > 0 && (
1568
- <div className={styles.reportSection}>
1881
+ <div className={styles.reportSection}>
1882
+ <div
1883
+ className={styles.sectionHeader}
1884
+ onClick={() =>
1885
+ setShowJoinTables(!showJoinTables)
1886
+ }
1887
+ style={{ cursor: 'pointer' }}
1888
+ title={
1889
+ showJoinTables
1890
+ ? 'Hide join tables'
1891
+ : 'Show join tables'
1892
+ }
1893
+ >
1569
1894
  <h3>Join Tables</h3>
1570
- {selectedTable &&
1571
- joins.map((join, index) => (
1572
- <div
1573
- key={index}
1574
- className={styles.joinSection}
1895
+ <div
1896
+ className={`${styles.toggleBtn} ${
1897
+ showJoinTables
1898
+ ? styles.toggleBtnActive
1899
+ : ''
1900
+ }`}
1901
+ >
1902
+ {showJoinTables ? '▲' : '▼'}
1903
+ </div>
1904
+ </div>
1905
+
1906
+ {showJoinTables && selectedTable && (
1907
+ <>
1908
+ <div
1909
+ className={
1910
+ styles.addManualJoinContainer
1911
+ }
1912
+ >
1913
+ <button
1914
+ className={styles.btn}
1915
+ onClick={handleAddJoin}
1575
1916
  >
1576
- <div className={styles.joinHeader}>
1577
- <h4>
1578
- {join.targetTable
1579
- ? `${formatName(
1580
- join.sourceTable
1581
- )} ${join.joinType.replace(
1582
- ' JOIN',
1583
- ''
1584
- )} ${formatName(
1585
- join.targetTable
1586
- )}`
1587
- : `Join #${index + 1}`}
1588
- </h4>
1589
- <button
1590
- className={
1591
- styles.removeJoinBtn
1592
- }
1593
- onClick={() =>
1594
- handleRemoveJoin(index)
1595
- }
1596
- title="Remove Join"
1597
- >
1598
- ×
1599
- </button>
1600
- </div>
1917
+ Add Manual Relationship
1918
+ </button>
1919
+ </div>
1601
1920
 
1602
- <div className={styles.joinGrid}>
1921
+ {joins.length > 0 && (
1922
+ <div
1923
+ className={
1924
+ styles.joinTablesContainer
1925
+ }
1926
+ >
1927
+ {joins.map((join, index) => (
1603
1928
  <div
1929
+ key={index}
1604
1930
  className={
1605
- styles.joinGridColumn
1931
+ styles.joinSection
1606
1932
  }
1607
1933
  >
1608
1934
  <div
1609
1935
  className={
1610
- styles.joinField
1936
+ styles.joinHeader
1611
1937
  }
1612
1938
  >
1613
- <label>
1614
- Join Type:
1615
- </label>
1616
- <select
1939
+ <h4>
1940
+ {join.targetTable
1941
+ ? `${formatName(
1942
+ join.sourceTable
1943
+ )} ${join.joinType.replace(
1944
+ ' JOIN',
1945
+ ''
1946
+ )} ${formatName(
1947
+ join.targetTable
1948
+ )}`
1949
+ : `Join #${
1950
+ index + 1
1951
+ }`}
1952
+ </h4>
1953
+ <button
1617
1954
  className={
1618
- styles.selectControl
1619
- }
1620
- value={
1621
- join.joinType
1955
+ styles.removeJoinBtn
1622
1956
  }
1623
- onChange={(e) =>
1624
- handleJoinChange(
1625
- index,
1626
- 'joinType',
1627
- e.target
1628
- .value
1957
+ onClick={() =>
1958
+ handleRemoveJoin(
1959
+ index
1629
1960
  )
1630
1961
  }
1962
+ title="Remove Join"
1631
1963
  >
1632
- <option value="INNER JOIN">
1633
- INNER JOIN
1634
- </option>
1635
- <option value="LEFT JOIN">
1636
- LEFT JOIN
1637
- </option>
1638
- <option value="RIGHT JOIN">
1639
- RIGHT JOIN
1640
- </option>
1641
- <option value="FULL JOIN">
1642
- FULL JOIN
1643
- </option>
1644
- </select>
1645
- </div>
1646
-
1647
- <div
1648
- className={
1649
- styles.joinField
1650
- }
1651
- >
1652
- <label>
1653
- Source Table:
1654
- </label>
1655
- <input
1656
- type="text"
1657
- className={
1658
- styles.formControl
1659
- }
1660
- value={formatName(
1661
- join.sourceTable
1662
- )}
1663
- readOnly
1664
- />
1964
+ ×
1965
+ </button>
1665
1966
  </div>
1666
1967
 
1667
1968
  <div
1668
1969
  className={
1669
- styles.joinField
1970
+ styles.joinGrid
1670
1971
  }
1671
1972
  >
1672
- <label>
1673
- Source Column:
1674
- </label>
1675
- <select
1973
+ <div
1676
1974
  className={
1677
- styles.selectControl
1678
- }
1679
- value={
1680
- join.sourceColumn
1681
- }
1682
- onChange={(e) =>
1683
- handleJoinChange(
1684
- index,
1685
- 'sourceColumn',
1686
- e.target
1687
- .value
1688
- )
1975
+ styles.joinGridColumn
1689
1976
  }
1690
1977
  >
1691
- <option value="">
1692
- Select Column
1693
- </option>
1694
- {tableColumns.map(
1695
- (column) => (
1696
- <option
1697
- key={
1698
- column.name
1699
- }
1700
- value={
1701
- column.name
1702
- }
1703
- >
1704
- {formatName(
1705
- column.name
1706
- )}
1978
+ <div
1979
+ className={
1980
+ styles.joinField
1981
+ }
1982
+ >
1983
+ <label>
1984
+ Join Type:
1985
+ </label>
1986
+ <select
1987
+ className={
1988
+ styles.selectControl
1989
+ }
1990
+ value={
1991
+ join.joinType
1992
+ }
1993
+ onChange={(
1994
+ e
1995
+ ) =>
1996
+ handleJoinChange(
1997
+ index,
1998
+ 'joinType',
1999
+ e
2000
+ .target
2001
+ .value
2002
+ )
2003
+ }
2004
+ >
2005
+ <option value="INNER JOIN">
2006
+ INNER
2007
+ JOIN
1707
2008
  </option>
1708
- )
1709
- )}
1710
- </select>
1711
- </div>
1712
- </div>
1713
-
1714
- <div
1715
- className={
1716
- styles.joinGridColumn
1717
- }
1718
- >
1719
- <div
1720
- className={
1721
- styles.joinField
1722
- }
1723
- >
1724
- <label>
1725
- Target Table:
1726
- </label>
1727
- <select
1728
- className={
1729
- styles.selectControl
1730
- }
1731
- value={
1732
- join.targetTable
1733
- }
1734
- onChange={(e) =>
1735
- handleJoinChange(
1736
- index,
1737
- 'targetTable',
1738
- e.target
1739
- .value
1740
- )
1741
- }
1742
- >
1743
- <option value="">
1744
- Select Table
1745
- </option>
1746
- {availableTables.map(
1747
- (table) => (
1748
- <option
1749
- key={
1750
- table.name
1751
- }
1752
- value={
1753
- table.name
1754
- }
1755
- >
1756
- {formatName(
1757
- table.name
1758
- )}
2009
+ <option value="LEFT JOIN">
2010
+ LEFT
2011
+ JOIN
1759
2012
  </option>
1760
- )
1761
- )}
1762
- </select>
1763
- </div>
2013
+ <option value="RIGHT JOIN">
2014
+ RIGHT
2015
+ JOIN
2016
+ </option>
2017
+ <option value="FULL JOIN">
2018
+ FULL
2019
+ JOIN
2020
+ </option>
2021
+ </select>
2022
+ </div>
1764
2023
 
1765
- {join.targetTable &&
1766
- join.availableColumns && (
1767
2024
  <div
1768
2025
  className={
1769
2026
  styles.joinField
1770
2027
  }
1771
2028
  >
1772
2029
  <label>
1773
- Target
2030
+ Source
2031
+ Table:
2032
+ </label>
2033
+ <input
2034
+ type="text"
2035
+ className={
2036
+ styles.formControl
2037
+ }
2038
+ value={formatName(
2039
+ join.sourceTable
2040
+ )}
2041
+ readOnly
2042
+ />
2043
+ </div>
2044
+
2045
+ <div
2046
+ className={
2047
+ styles.joinField
2048
+ }
2049
+ >
2050
+ <label>
2051
+ Source
1774
2052
  Column:
1775
2053
  </label>
1776
2054
  <select
@@ -1778,14 +2056,14 @@ const GenericReport = ({ setting = {} }) => {
1778
2056
  styles.selectControl
1779
2057
  }
1780
2058
  value={
1781
- join.targetColumn
2059
+ join.sourceColumn
1782
2060
  }
1783
2061
  onChange={(
1784
2062
  e
1785
2063
  ) =>
1786
2064
  handleJoinChange(
1787
2065
  index,
1788
- 'targetColumn',
2066
+ 'sourceColumn',
1789
2067
  e
1790
2068
  .target
1791
2069
  .value
@@ -1796,7 +2074,7 @@ const GenericReport = ({ setting = {} }) => {
1796
2074
  Select
1797
2075
  Column
1798
2076
  </option>
1799
- {join.availableColumns.map(
2077
+ {tableColumns.map(
1800
2078
  (
1801
2079
  column
1802
2080
  ) => (
@@ -1816,24 +2094,134 @@ const GenericReport = ({ setting = {} }) => {
1816
2094
  )}
1817
2095
  </select>
1818
2096
  </div>
1819
- )}
2097
+ </div>
2098
+
2099
+ <div
2100
+ className={
2101
+ styles.joinGridColumn
2102
+ }
2103
+ >
2104
+ <div
2105
+ className={
2106
+ styles.joinField
2107
+ }
2108
+ >
2109
+ <label>
2110
+ Target
2111
+ Table:
2112
+ </label>
2113
+ <select
2114
+ className={
2115
+ styles.selectControl
2116
+ }
2117
+ value={
2118
+ join.targetTable
2119
+ }
2120
+ onChange={(
2121
+ e
2122
+ ) =>
2123
+ handleJoinChange(
2124
+ index,
2125
+ 'targetTable',
2126
+ e
2127
+ .target
2128
+ .value
2129
+ )
2130
+ }
2131
+ >
2132
+ <option value="">
2133
+ Select
2134
+ Table
2135
+ </option>
2136
+ {availableTables.map(
2137
+ (
2138
+ table
2139
+ ) => (
2140
+ <option
2141
+ key={
2142
+ table.name
2143
+ }
2144
+ value={
2145
+ table.name
2146
+ }
2147
+ >
2148
+ {formatName(
2149
+ table.name
2150
+ )}
2151
+ </option>
2152
+ )
2153
+ )}
2154
+ </select>
2155
+ </div>
2156
+
2157
+ {join.targetTable &&
2158
+ join.availableColumns && (
2159
+ <div
2160
+ className={
2161
+ styles.joinField
2162
+ }
2163
+ >
2164
+ <label>
2165
+ Target
2166
+ Column:
2167
+ </label>
2168
+ <select
2169
+ className={
2170
+ styles.selectControl
2171
+ }
2172
+ value={
2173
+ join.targetColumn
2174
+ }
2175
+ onChange={(
2176
+ e
2177
+ ) =>
2178
+ handleJoinChange(
2179
+ index,
2180
+ 'targetColumn',
2181
+ e
2182
+ .target
2183
+ .value
2184
+ )
2185
+ }
2186
+ >
2187
+ <option value="">
2188
+ Select
2189
+ Column
2190
+ </option>
2191
+ {join.availableColumns.map(
2192
+ (
2193
+ column
2194
+ ) => (
2195
+ <option
2196
+ key={
2197
+ column.name
2198
+ }
2199
+ value={
2200
+ column.name
2201
+ }
2202
+ >
2203
+ {formatName(
2204
+ column.name
2205
+ )}
2206
+ </option>
2207
+ )
2208
+ )}
2209
+ </select>
2210
+ </div>
2211
+ )}
2212
+ </div>
2213
+ </div>
1820
2214
  </div>
1821
- </div>
2215
+ ))}
1822
2216
  </div>
1823
- ))}
1824
- </div>
1825
- )}
2217
+ )}
2218
+ </>
2219
+ )}
2220
+ </div>
1826
2221
 
1827
2222
  <div className={styles.addJoinButtonContainer}>
1828
2223
  {selectedTable && (
1829
2224
  <>
1830
- <button
1831
- className={styles.btn}
1832
- onClick={handleAddJoin}
1833
- >
1834
- Add Manual Relationship
1835
- </button>
1836
-
1837
2225
  {/* Main table suggested relationships */}
1838
2226
  {suggestedJoins['main'] &&
1839
2227
  suggestedJoins['main'].length > 0 && (
@@ -1842,89 +2230,151 @@ const GenericReport = ({ setting = {} }) => {
1842
2230
  styles.suggestedJoinsSection
1843
2231
  }
1844
2232
  >
1845
- <h4>
1846
- Suggested Relationships for{' '}
1847
- {formatName(selectedTable)}
1848
- </h4>
1849
- <p className={styles.helpText}>
1850
- Auto-detected based on
1851
- database structure.
1852
- <span
1853
- className={
1854
- styles.ratingInfo
1855
- }
1856
- >
1857
- ⭐⭐⭐ = High confidence
1858
- | ⭐⭐ = Medium
1859
- confidence | ⭐ = Low
1860
- confidence
1861
- </span>
1862
- </p>
1863
2233
  <div
1864
2234
  className={
1865
- styles.suggestedJoinsList
2235
+ styles.sectionHeader
1866
2236
  }
1867
2237
  >
1868
- {suggestedJoins['main'].map(
1869
- (join, index) => (
1870
- <div
1871
- key={index}
2238
+ <h4>
2239
+ Suggested Relationships
2240
+ for{' '}
2241
+ {formatName(
2242
+ selectedTable
2243
+ )}
2244
+ </h4>
2245
+ <button
2246
+ className={`${
2247
+ styles.toggleBtn
2248
+ } ${
2249
+ showSuggestedRelationships[
2250
+ 'main'
2251
+ ]
2252
+ ? styles.toggleBtnActive
2253
+ : ''
2254
+ }`}
2255
+ onClick={() =>
2256
+ setShowSuggestedRelationships(
2257
+ (prev) => ({
2258
+ ...prev,
2259
+ main: !prev[
2260
+ 'main'
2261
+ ],
2262
+ })
2263
+ )
2264
+ }
2265
+ title={
2266
+ showSuggestedRelationships[
2267
+ 'main'
2268
+ ]
2269
+ ? 'Hide relationships'
2270
+ : 'Show relationships'
2271
+ }
2272
+ >
2273
+ {showSuggestedRelationships[
2274
+ 'main'
2275
+ ]
2276
+ ? '▲'
2277
+ : '▼'}
2278
+ </button>
2279
+ </div>
2280
+ {showSuggestedRelationships[
2281
+ 'main'
2282
+ ] && (
2283
+ <>
2284
+ <p
2285
+ className={
2286
+ styles.helpText
2287
+ }
2288
+ >
2289
+ Auto-detected based
2290
+ on database
2291
+ structure.
2292
+ <span
1872
2293
  className={
1873
- styles.suggestedJoinItem
2294
+ styles.ratingInfo
1874
2295
  }
1875
- title={formatRelationshipDescription(
1876
- join.description,
1877
- false,
1878
- join
1879
- )}
1880
2296
  >
1881
- <div
1882
- className={
1883
- styles.suggestedJoinInfo
1884
- }
1885
- >
1886
- <span
2297
+ ⭐⭐⭐ = High
2298
+ confidence |
2299
+ ⭐⭐ = Medium
2300
+ confidence | ⭐
2301
+ = Low confidence
2302
+ </span>
2303
+ </p>
2304
+ <div
2305
+ className={
2306
+ styles.suggestedJoinsList
2307
+ }
2308
+ >
2309
+ {suggestedJoins[
2310
+ 'main'
2311
+ ].map(
2312
+ (
2313
+ join,
2314
+ index
2315
+ ) => (
2316
+ <div
2317
+ key={
2318
+ index
2319
+ }
1887
2320
  className={
1888
- styles.suggestedJoinDescription
2321
+ styles.suggestedJoinItem
1889
2322
  }
1890
- >
1891
- {formatRelationshipDescription(
2323
+ title={formatRelationshipDescription(
1892
2324
  join.description,
1893
- true,
2325
+ false,
1894
2326
  join
1895
2327
  )}
1896
- </span>
1897
- <span
1898
- className={
1899
- styles.confidenceBadge
1900
- }
1901
2328
  >
1902
- {join.confidence ===
1903
- 'high'
1904
- ? '⭐⭐⭐'
1905
- : join.confidence ===
1906
- 'medium'
1907
- ? '⭐⭐'
1908
- : '⭐'}
1909
- </span>
1910
- </div>
1911
- <button
1912
- className={
1913
- styles.addSuggestedJoinBtn
1914
- }
1915
- onClick={() =>
1916
- handleAddSuggestedJoin(
1917
- join
1918
- )
1919
- }
1920
- title="Add this relationship"
1921
- >
1922
- Add
1923
- </button>
1924
- </div>
1925
- )
1926
- )}
1927
- </div>
2329
+ <div
2330
+ className={
2331
+ styles.suggestedJoinInfo
2332
+ }
2333
+ >
2334
+ <span
2335
+ className={
2336
+ styles.suggestedJoinDescription
2337
+ }
2338
+ >
2339
+ {formatRelationshipDescription(
2340
+ join.description,
2341
+ true,
2342
+ join
2343
+ )}
2344
+ </span>
2345
+ <span
2346
+ className={
2347
+ styles.confidenceBadge
2348
+ }
2349
+ >
2350
+ {join.confidence ===
2351
+ 'high'
2352
+ ? '⭐⭐⭐'
2353
+ : join.confidence ===
2354
+ 'medium'
2355
+ ? '⭐⭐'
2356
+ : '⭐'}
2357
+ </span>
2358
+ </div>
2359
+ <button
2360
+ className={
2361
+ styles.addSuggestedJoinBtn
2362
+ }
2363
+ onClick={() =>
2364
+ handleAddSuggestedJoin(
2365
+ join
2366
+ )
2367
+ }
2368
+ title="Add this relationship"
2369
+ >
2370
+ Add
2371
+ </button>
2372
+ </div>
2373
+ )
2374
+ )}
2375
+ </div>
2376
+ </>
2377
+ )}
1928
2378
  </div>
1929
2379
  )}
1930
2380
 
@@ -1953,87 +2403,139 @@ const GenericReport = ({ setting = {} }) => {
1953
2403
  styles.suggestedJoinsSection
1954
2404
  }
1955
2405
  >
1956
- <h4>
1957
- Suggested
1958
- Relationships
1959
- for{' '}
1960
- {formatName(
1961
- join.targetTable
1962
- )}
1963
- </h4>
1964
2406
  <div
1965
2407
  className={
1966
- styles.suggestedJoinsList
2408
+ styles.sectionHeader
1967
2409
  }
1968
2410
  >
1969
- {suggestedJoins[
1970
- join
1971
- .targetTable
1972
- ].map(
1973
- (
1974
- suggestedJoin,
1975
- index
1976
- ) => (
1977
- <div
1978
- key={
1979
- index
1980
- }
1981
- className={
1982
- styles.suggestedJoinItem
1983
- }
1984
- title={formatRelationshipDescription(
1985
- suggestedJoin.description,
1986
- false,
1987
- suggestedJoin
1988
- )}
1989
- >
2411
+ <h4>
2412
+ Suggested
2413
+ Relationships
2414
+ for{' '}
2415
+ {formatName(
2416
+ join.targetTable
2417
+ )}
2418
+ </h4>
2419
+ <button
2420
+ className={`${
2421
+ styles.toggleBtn
2422
+ } ${
2423
+ showSuggestedRelationships[
2424
+ join
2425
+ .targetTable
2426
+ ]
2427
+ ? styles.toggleBtnActive
2428
+ : ''
2429
+ }`}
2430
+ onClick={() =>
2431
+ setShowSuggestedRelationships(
2432
+ (
2433
+ prev
2434
+ ) => ({
2435
+ ...prev,
2436
+ [join.targetTable]:
2437
+ !prev[
2438
+ join
2439
+ .targetTable
2440
+ ],
2441
+ })
2442
+ )
2443
+ }
2444
+ title={
2445
+ showSuggestedRelationships[
2446
+ join
2447
+ .targetTable
2448
+ ]
2449
+ ? 'Hide relationships'
2450
+ : 'Show relationships'
2451
+ }
2452
+ >
2453
+ {showSuggestedRelationships[
2454
+ join
2455
+ .targetTable
2456
+ ]
2457
+ ? '▲'
2458
+ : '▼'}
2459
+ </button>
2460
+ </div>
2461
+ {showSuggestedRelationships[
2462
+ join
2463
+ .targetTable
2464
+ ] && (
2465
+ <div
2466
+ className={
2467
+ styles.suggestedJoinsList
2468
+ }
2469
+ >
2470
+ {suggestedJoins[
2471
+ join
2472
+ .targetTable
2473
+ ].map(
2474
+ (
2475
+ suggestedJoin,
2476
+ index
2477
+ ) => (
1990
2478
  <div
2479
+ key={
2480
+ index
2481
+ }
1991
2482
  className={
1992
- styles.suggestedJoinInfo
2483
+ styles.suggestedJoinItem
1993
2484
  }
2485
+ title={formatRelationshipDescription(
2486
+ suggestedJoin.description,
2487
+ false,
2488
+ suggestedJoin
2489
+ )}
1994
2490
  >
1995
- <span
2491
+ <div
1996
2492
  className={
1997
- styles.suggestedJoinDescription
2493
+ styles.suggestedJoinInfo
1998
2494
  }
1999
2495
  >
2000
- {formatRelationshipDescription(
2001
- suggestedJoin.description,
2002
- true,
2003
- suggestedJoin
2004
- )}
2005
- </span>
2006
- <span
2496
+ <span
2497
+ className={
2498
+ styles.suggestedJoinDescription
2499
+ }
2500
+ >
2501
+ {formatRelationshipDescription(
2502
+ suggestedJoin.description,
2503
+ true,
2504
+ suggestedJoin
2505
+ )}
2506
+ </span>
2507
+ <span
2508
+ className={
2509
+ styles.confidenceBadge
2510
+ }
2511
+ >
2512
+ {suggestedJoin.confidence ===
2513
+ 'high'
2514
+ ? '⭐⭐⭐'
2515
+ : suggestedJoin.confidence ===
2516
+ 'medium'
2517
+ ? '⭐⭐'
2518
+ : '⭐'}
2519
+ </span>
2520
+ </div>
2521
+ <button
2007
2522
  className={
2008
- styles.confidenceBadge
2523
+ styles.addSuggestedJoinBtn
2009
2524
  }
2525
+ onClick={() =>
2526
+ handleAddSuggestedJoin(
2527
+ suggestedJoin
2528
+ )
2529
+ }
2530
+ title="Add this relationship"
2010
2531
  >
2011
- {suggestedJoin.confidence ===
2012
- 'high'
2013
- ? '⭐⭐⭐'
2014
- : suggestedJoin.confidence ===
2015
- 'medium'
2016
- ? '⭐⭐'
2017
- : '⭐'}
2018
- </span>
2532
+ Add
2533
+ </button>
2019
2534
  </div>
2020
- <button
2021
- className={
2022
- styles.addSuggestedJoinBtn
2023
- }
2024
- onClick={() =>
2025
- handleAddSuggestedJoin(
2026
- suggestedJoin
2027
- )
2028
- }
2029
- title="Add this relationship"
2030
- >
2031
- Add
2032
- </button>
2033
- </div>
2034
- )
2035
- )}
2036
- </div>
2535
+ )
2536
+ )}
2537
+ </div>
2538
+ )}
2037
2539
  </div>
2038
2540
  )}
2039
2541
 
@@ -2060,6 +2562,600 @@ const GenericReport = ({ setting = {} }) => {
2060
2562
  )}
2061
2563
  </div>
2062
2564
 
2565
+ {/* Sorting Section */}
2566
+ <div className={styles.reportSection}>
2567
+ <div
2568
+ className={styles.sectionHeader}
2569
+ onClick={() =>
2570
+ setShowSortingSection(!showSortingSection)
2571
+ }
2572
+ style={{ cursor: 'pointer' }}
2573
+ title={
2574
+ showSortingSection
2575
+ ? 'Hide sorting options'
2576
+ : 'Show sorting options'
2577
+ }
2578
+ >
2579
+ <h3>Sorting</h3>
2580
+ <div
2581
+ className={`${styles.toggleBtn} ${
2582
+ showSortingSection
2583
+ ? styles.toggleBtnActive
2584
+ : ''
2585
+ }`}
2586
+ >
2587
+ {showSortingSection ? '▲' : '▼'}
2588
+ </div>
2589
+ </div>
2590
+
2591
+ {showSortingSection && (
2592
+ <>
2593
+ <div
2594
+ className={
2595
+ styles.addManualJoinContainer
2596
+ }
2597
+ >
2598
+ <button
2599
+ className={styles.btn}
2600
+ onClick={handleAddSortCriterion}
2601
+ disabled={
2602
+ selectedColumns.length === 0
2603
+ }
2604
+ >
2605
+ Add Sort Criterion
2606
+ </button>
2607
+ </div>
2608
+
2609
+ {sortCriteria.length > 0 && (
2610
+ <div
2611
+ className={
2612
+ styles.joinTablesContainer
2613
+ }
2614
+ >
2615
+ {sortCriteria.map(
2616
+ (criterion, index) => (
2617
+ <div
2618
+ key={index}
2619
+ className={
2620
+ styles.joinSection
2621
+ }
2622
+ >
2623
+ <div
2624
+ className={
2625
+ styles.joinHeader
2626
+ }
2627
+ >
2628
+ <h4>
2629
+ Sort by{' '}
2630
+ {criterion.table &&
2631
+ criterion.column
2632
+ ? `${formatName(
2633
+ criterion.table
2634
+ )}.${formatName(
2635
+ criterion.column
2636
+ )}`
2637
+ : `Criterion #${
2638
+ index +
2639
+ 1
2640
+ }`}
2641
+ </h4>
2642
+ <button
2643
+ className={
2644
+ styles.removeJoinBtn
2645
+ }
2646
+ onClick={() =>
2647
+ handleRemoveSortCriterion(
2648
+ index
2649
+ )
2650
+ }
2651
+ title="Remove Sort Criterion"
2652
+ >
2653
+ ×
2654
+ </button>
2655
+ </div>
2656
+
2657
+ <div
2658
+ className={
2659
+ styles.joinGrid
2660
+ }
2661
+ >
2662
+ <div
2663
+ className={
2664
+ styles.joinGridColumn
2665
+ }
2666
+ >
2667
+ <div
2668
+ className={
2669
+ styles.joinField
2670
+ }
2671
+ >
2672
+ <label>
2673
+ Column:
2674
+ </label>
2675
+ <select
2676
+ className={
2677
+ styles.selectControl
2678
+ }
2679
+ value={`${criterion.table}.${criterion.column}`}
2680
+ onChange={(
2681
+ e
2682
+ ) => {
2683
+ const [
2684
+ table,
2685
+ column,
2686
+ ] =
2687
+ e.target.value.split(
2688
+ '.'
2689
+ );
2690
+ handleSortCriterionChange(
2691
+ index,
2692
+ 'table',
2693
+ table
2694
+ );
2695
+ handleSortCriterionChange(
2696
+ index,
2697
+ 'column',
2698
+ column
2699
+ );
2700
+ }}
2701
+ >
2702
+ <option value="">
2703
+ Select
2704
+ Column
2705
+ </option>
2706
+ {/* Main table columns */}
2707
+ <optgroup
2708
+ label={`${formatName(
2709
+ selectedTable
2710
+ )} Columns`}
2711
+ >
2712
+ {tableColumns.map(
2713
+ (
2714
+ column
2715
+ ) => (
2716
+ <option
2717
+ key={`${selectedTable}.${column.name}`}
2718
+ value={`${selectedTable}.${column.name}`}
2719
+ >
2720
+ {formatName(
2721
+ column.name
2722
+ )}
2723
+ </option>
2724
+ )
2725
+ )}
2726
+ </optgroup>
2727
+
2728
+ {/* Joined tables columns */}
2729
+ {joins.map(
2730
+ (
2731
+ join,
2732
+ joinIndex
2733
+ ) =>
2734
+ join.targetTable &&
2735
+ join.availableColumns &&
2736
+ join
2737
+ .availableColumns
2738
+ .length >
2739
+ 0 ? (
2740
+ <optgroup
2741
+ key={`join-${joinIndex}`}
2742
+ label={`${formatName(
2743
+ join.targetTable
2744
+ )} Columns`}
2745
+ >
2746
+ {join.availableColumns.map(
2747
+ (
2748
+ column
2749
+ ) => (
2750
+ <option
2751
+ key={`${join.targetTable}.${column.name}`}
2752
+ value={`${join.targetTable}.${column.name}`}
2753
+ >
2754
+ {formatName(
2755
+ column.name
2756
+ )}
2757
+ </option>
2758
+ )
2759
+ )}
2760
+ </optgroup>
2761
+ ) : null
2762
+ )}
2763
+ </select>
2764
+ </div>
2765
+ </div>
2766
+
2767
+ <div
2768
+ className={
2769
+ styles.joinGridColumn
2770
+ }
2771
+ >
2772
+ <div
2773
+ className={
2774
+ styles.joinField
2775
+ }
2776
+ >
2777
+ <label>
2778
+ Direction:
2779
+ </label>
2780
+ <select
2781
+ className={
2782
+ styles.selectControl
2783
+ }
2784
+ value={
2785
+ criterion.direction
2786
+ }
2787
+ onChange={(
2788
+ e
2789
+ ) =>
2790
+ handleSortCriterionChange(
2791
+ index,
2792
+ 'direction',
2793
+ e
2794
+ .target
2795
+ .value
2796
+ )
2797
+ }
2798
+ >
2799
+ <option value="ASC">
2800
+ Ascending
2801
+ </option>
2802
+ <option value="DESC">
2803
+ Descending
2804
+ </option>
2805
+ </select>
2806
+ </div>
2807
+ </div>
2808
+ </div>
2809
+ </div>
2810
+ )
2811
+ )}
2812
+ </div>
2813
+ )}
2814
+ </>
2815
+ )}
2816
+ </div>
2817
+
2818
+ {/* Filtering Section */}
2819
+ <div className={styles.reportSection}>
2820
+ <div
2821
+ className={styles.sectionHeader}
2822
+ onClick={() =>
2823
+ setShowFilteringSection(
2824
+ !showFilteringSection
2825
+ )
2826
+ }
2827
+ style={{ cursor: 'pointer' }}
2828
+ title={
2829
+ showFilteringSection
2830
+ ? 'Hide filtering options'
2831
+ : 'Show filtering options'
2832
+ }
2833
+ >
2834
+ <h3>Filtering</h3>
2835
+ <div
2836
+ className={`${styles.toggleBtn} ${
2837
+ showFilteringSection
2838
+ ? styles.toggleBtnActive
2839
+ : ''
2840
+ }`}
2841
+ >
2842
+ {showFilteringSection ? '▲' : '▼'}
2843
+ </div>
2844
+ </div>
2845
+
2846
+ {showFilteringSection && (
2847
+ <>
2848
+ <div
2849
+ className={
2850
+ styles.addManualJoinContainer
2851
+ }
2852
+ >
2853
+ <button
2854
+ className={styles.btn}
2855
+ onClick={handleAddFilterCriterion}
2856
+ disabled={
2857
+ selectedColumns.length === 0
2858
+ }
2859
+ >
2860
+ Add Filter Criterion
2861
+ </button>
2862
+ </div>
2863
+
2864
+ {filterCriteria.length > 0 && (
2865
+ <div
2866
+ className={
2867
+ styles.joinTablesContainer
2868
+ }
2869
+ >
2870
+ {filterCriteria.map(
2871
+ (criterion, index) => (
2872
+ <div
2873
+ key={index}
2874
+ className={
2875
+ styles.joinSection
2876
+ }
2877
+ >
2878
+ <div
2879
+ className={
2880
+ styles.joinHeader
2881
+ }
2882
+ >
2883
+ <h4>
2884
+ Filter on{' '}
2885
+ {criterion.table &&
2886
+ criterion.column
2887
+ ? `${formatName(
2888
+ criterion.table
2889
+ )}.${formatName(
2890
+ criterion.column
2891
+ )}`
2892
+ : `Criterion #${
2893
+ index +
2894
+ 1
2895
+ }`}
2896
+ </h4>
2897
+ <button
2898
+ className={
2899
+ styles.removeJoinBtn
2900
+ }
2901
+ onClick={() =>
2902
+ handleRemoveFilterCriterion(
2903
+ index
2904
+ )
2905
+ }
2906
+ title="Remove Filter Criterion"
2907
+ >
2908
+ ×
2909
+ </button>
2910
+ </div>
2911
+
2912
+ <div
2913
+ className={
2914
+ styles.joinGrid
2915
+ }
2916
+ >
2917
+ <div
2918
+ className={
2919
+ styles.joinGridColumn
2920
+ }
2921
+ >
2922
+ <div
2923
+ className={
2924
+ styles.joinField
2925
+ }
2926
+ >
2927
+ <label>
2928
+ Column:
2929
+ </label>
2930
+ <select
2931
+ className={
2932
+ styles.selectControl
2933
+ }
2934
+ value={`${criterion.table}.${criterion.column}`}
2935
+ onChange={(
2936
+ e
2937
+ ) => {
2938
+ const [
2939
+ table,
2940
+ column,
2941
+ ] =
2942
+ e.target.value.split(
2943
+ '.'
2944
+ );
2945
+ handleFilterCriterionChange(
2946
+ index,
2947
+ 'table',
2948
+ table
2949
+ );
2950
+ handleFilterCriterionChange(
2951
+ index,
2952
+ 'column',
2953
+ column
2954
+ );
2955
+ }}
2956
+ >
2957
+ <option value="">
2958
+ Select
2959
+ Column
2960
+ </option>
2961
+ {/* Main table columns */}
2962
+ <optgroup
2963
+ label={`${formatName(
2964
+ selectedTable
2965
+ )} Columns`}
2966
+ >
2967
+ {tableColumns.map(
2968
+ (
2969
+ column
2970
+ ) => (
2971
+ <option
2972
+ key={`${selectedTable}.${column.name}`}
2973
+ value={`${selectedTable}.${column.name}`}
2974
+ >
2975
+ {formatName(
2976
+ column.name
2977
+ )}
2978
+ </option>
2979
+ )
2980
+ )}
2981
+ </optgroup>
2982
+
2983
+ {/* Joined tables columns */}
2984
+ {joins.map(
2985
+ (
2986
+ join,
2987
+ joinIndex
2988
+ ) =>
2989
+ join.targetTable &&
2990
+ join.availableColumns &&
2991
+ join
2992
+ .availableColumns
2993
+ .length >
2994
+ 0 ? (
2995
+ <optgroup
2996
+ key={`join-${joinIndex}`}
2997
+ label={`${formatName(
2998
+ join.targetTable
2999
+ )} Columns`}
3000
+ >
3001
+ {join.availableColumns.map(
3002
+ (
3003
+ column
3004
+ ) => (
3005
+ <option
3006
+ key={`${join.targetTable}.${column.name}`}
3007
+ value={`${join.targetTable}.${column.name}`}
3008
+ >
3009
+ {formatName(
3010
+ column.name
3011
+ )}
3012
+ </option>
3013
+ )
3014
+ )}
3015
+ </optgroup>
3016
+ ) : null
3017
+ )}
3018
+ </select>
3019
+ </div>
3020
+ </div>
3021
+
3022
+ <div
3023
+ className={
3024
+ styles.joinGridColumn
3025
+ }
3026
+ >
3027
+ <div
3028
+ className={
3029
+ styles.joinField
3030
+ }
3031
+ >
3032
+ <label>
3033
+ Operator:
3034
+ </label>
3035
+ <select
3036
+ className={
3037
+ styles.selectControl
3038
+ }
3039
+ value={
3040
+ criterion.operator
3041
+ }
3042
+ onChange={(
3043
+ e
3044
+ ) =>
3045
+ handleFilterCriterionChange(
3046
+ index,
3047
+ 'operator',
3048
+ e
3049
+ .target
3050
+ .value
3051
+ )
3052
+ }
3053
+ >
3054
+ <option value="=">
3055
+ Equals
3056
+ (=)
3057
+ </option>
3058
+ <option value="!=">
3059
+ Not
3060
+ Equals
3061
+ (!=)
3062
+ </option>
3063
+ <option value="&gt;">
3064
+ Greater
3065
+ Than
3066
+ (&gt;)
3067
+ </option>
3068
+ <option value="&lt;">
3069
+ Less
3070
+ Than
3071
+ (&lt;)
3072
+ </option>
3073
+ <option value="&gt;=">
3074
+ Greater
3075
+ Than
3076
+ or
3077
+ Equal
3078
+ (&gt;=)
3079
+ </option>
3080
+ <option value="&lt;=">
3081
+ Less
3082
+ Than
3083
+ or
3084
+ Equal
3085
+ (&lt;=)
3086
+ </option>
3087
+ <option value="LIKE">
3088
+ Contains
3089
+ (LIKE)
3090
+ </option>
3091
+ <option value="NOT LIKE">
3092
+ Does
3093
+ Not
3094
+ Contain
3095
+ (NOT
3096
+ LIKE)
3097
+ </option>
3098
+ <option value="IS NULL">
3099
+ Is
3100
+ Empty
3101
+ (NULL)
3102
+ </option>
3103
+ <option value="IS NOT NULL">
3104
+ Is
3105
+ Not
3106
+ Empty
3107
+ (NOT
3108
+ NULL)
3109
+ </option>
3110
+ </select>
3111
+ </div>
3112
+
3113
+ {criterion.operator !==
3114
+ 'IS NULL' &&
3115
+ criterion.operator !==
3116
+ 'IS NOT NULL' && (
3117
+ <div
3118
+ className={
3119
+ styles.joinField
3120
+ }
3121
+ >
3122
+ <label>
3123
+ Value:
3124
+ </label>
3125
+ <input
3126
+ type="text"
3127
+ className={
3128
+ styles.formControl
3129
+ }
3130
+ value={
3131
+ criterion.value
3132
+ }
3133
+ onChange={(
3134
+ e
3135
+ ) =>
3136
+ handleFilterCriterionChange(
3137
+ index,
3138
+ 'value',
3139
+ e
3140
+ .target
3141
+ .value
3142
+ )
3143
+ }
3144
+ placeholder="Enter filter value"
3145
+ />
3146
+ </div>
3147
+ )}
3148
+ </div>
3149
+ </div>
3150
+ </div>
3151
+ )
3152
+ )}
3153
+ </div>
3154
+ )}
3155
+ </>
3156
+ )}
3157
+ </div>
3158
+
2063
3159
  <div className={styles.actionButtons}>
2064
3160
  <button
2065
3161
  className={styles.btn}