@visns-studio/visns-components 5.8.10 → 5.8.12

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.
@@ -8,7 +8,7 @@ import get from 'lodash/get';
8
8
  import { saveAs } from 'file-saver';
9
9
  import { toast } from 'react-toastify';
10
10
  import { arrayMoveImmutable } from 'array-move';
11
- import { confirmDialog } from '../../utils/ConfirmDialog';
11
+ import { showConfirmDialog } from './ConfirmationDialog';
12
12
 
13
13
  import CustomFetch from '../../crm/Fetch';
14
14
  import ActionButtons from './ActionButtons';
@@ -34,7 +34,12 @@ function GenericQuote({ logo, setting, urlParam }) {
34
34
  const [formData, setFormData] = useState({});
35
35
 
36
36
  // Modal functions
37
- const modalOpen = (type, id, customFormSettings = null, itemData = null) => {
37
+ const modalOpen = (
38
+ type,
39
+ id,
40
+ customFormSettings = null,
41
+ itemData = null
42
+ ) => {
38
43
  // Debug logging to help troubleshoot form settings issues
39
44
  console.log('Opening modal with form settings:', {
40
45
  type,
@@ -42,7 +47,7 @@ function GenericQuote({ logo, setting, urlParam }) {
42
47
  hasCustomSettings: !!customFormSettings,
43
48
  customFormSettings,
44
49
  defaultForm: form,
45
- itemData
50
+ itemData,
46
51
  });
47
52
 
48
53
  // Use custom form settings if provided, otherwise use the default form
@@ -58,9 +63,10 @@ function GenericQuote({ logo, setting, urlParam }) {
58
63
  ...formSettings,
59
64
  create: {
60
65
  title: customFormSettings?.create?.title || 'Create New',
61
- submitUrl: customFormSettings?.create?.submitUrl || '/api/submit',
62
- method: customFormSettings?.create?.method || 'POST'
63
- }
66
+ submitUrl:
67
+ customFormSettings?.create?.submitUrl || '/api/submit',
68
+ method: customFormSettings?.create?.method || 'POST',
69
+ },
64
70
  };
65
71
  }
66
72
 
@@ -68,7 +74,7 @@ function GenericQuote({ logo, setting, urlParam }) {
68
74
  if (itemData && type === 'update') {
69
75
  formSettings = {
70
76
  ...formSettings,
71
- initialData: itemData
77
+ initialData: itemData,
72
78
  };
73
79
  }
74
80
 
@@ -93,13 +99,17 @@ function GenericQuote({ logo, setting, urlParam }) {
93
99
  const newData = { ...data };
94
100
 
95
101
  // Reorder the items in the specified table
96
- newData[tableId] = arrayMoveImmutable(newData[tableId], oldIndex, newIndex);
102
+ newData[tableId] = arrayMoveImmutable(
103
+ newData[tableId],
104
+ oldIndex,
105
+ newIndex
106
+ );
97
107
 
98
108
  // Update the state immediately for better UX
99
109
  setData(newData);
100
110
 
101
111
  // Find the table configuration
102
- const tableConfig = setting.tables.find(t => t.id === tableId);
112
+ const tableConfig = setting.tables.find((t) => t.id === tableId);
103
113
  if (!tableConfig || !tableConfig.urls || !tableConfig.urls.sort) {
104
114
  console.error('Sort URL not found for table:', tableId);
105
115
  toast.error('Failed to update item order: Missing configuration');
@@ -110,7 +120,9 @@ function GenericQuote({ logo, setting, urlParam }) {
110
120
  const { url, key } = tableConfig.urls.sort;
111
121
 
112
122
  // Get CSRF token
113
- const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
123
+ const csrfToken = document
124
+ .querySelector('meta[name="csrf-token"]')
125
+ ?.getAttribute('content');
114
126
 
115
127
  // Show a loading toast
116
128
  const toastId = toast.loading('Updating item order...');
@@ -123,7 +135,7 @@ function GenericQuote({ logo, setting, urlParam }) {
123
135
 
124
136
  // Create a request body with just the sort_order key
125
137
  const requestBody = {
126
- [key]: sortOrder
138
+ [key]: sortOrder,
127
139
  };
128
140
 
129
141
  // Send individual request for this item
@@ -134,9 +146,8 @@ function GenericQuote({ logo, setting, urlParam }) {
134
146
  'X-Requested-With': 'XMLHttpRequest',
135
147
  'X-CSRF-TOKEN': csrfToken,
136
148
  },
137
- body: JSON.stringify(requestBody)
138
- })
139
- .then(response => {
149
+ body: JSON.stringify(requestBody),
150
+ }).then((response) => {
140
151
  if (!response.ok) {
141
152
  throw new Error(`Failed to update item ${itemId}`);
142
153
  }
@@ -146,7 +157,7 @@ function GenericQuote({ logo, setting, urlParam }) {
146
157
 
147
158
  // Wait for all requests to complete
148
159
  Promise.all(updatePromises)
149
- .then(results => {
160
+ .then((results) => {
150
161
  console.log('All items updated successfully:', results);
151
162
 
152
163
  // Update toast
@@ -158,7 +169,7 @@ function GenericQuote({ logo, setting, urlParam }) {
158
169
  closeButton: true,
159
170
  });
160
171
  })
161
- .catch(error => {
172
+ .catch((error) => {
162
173
  console.error('Error updating order:', error);
163
174
 
164
175
  // Update toast
@@ -179,131 +190,302 @@ function GenericQuote({ logo, setting, urlParam }) {
179
190
  console.log('Edit item:', { tableId, row, index });
180
191
 
181
192
  // Find the table configuration
182
- const tableConfig = setting.tables.find(t => t.id === tableId);
193
+ const tableConfig = setting.tables.find((t) => t.id === tableId);
183
194
  if (!tableConfig) {
184
195
  console.error('Table configuration not found for:', tableId);
185
196
  toast.error('Failed to edit item: Missing configuration');
186
197
  return;
187
198
  }
188
199
 
189
- // Find the form settings for the table
190
- const tableForm = tableConfig.form || form;
200
+ // Find the addItem action to get its form settings
201
+ const addItemAction = actions.find((action) => action.id === 'addItem');
202
+ // Use the addItem action's settings if available, otherwise fall back to table form or default form
203
+ const tableForm = addItemAction?.setting || tableConfig.form || form;
191
204
 
192
205
  // Get the item ID
193
206
  const itemId = row[tableConfig.dataId];
194
207
 
208
+ console.info(row, tableConfig);
209
+
210
+ // Check if we have a valid item ID
211
+ if (!itemId) {
212
+ console.error('No valid item ID found for editing:', row);
213
+ toast.error('Failed to edit item: Missing ID');
214
+ return;
215
+ }
216
+
217
+ // Log the table configuration for debugging
218
+ console.log('Table config for editing:', tableConfig);
219
+
195
220
  // If there's a get URL for fetching item details
196
221
  if (tableConfig.urls && tableConfig.urls.get) {
197
222
  // Get CSRF token
198
- const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
223
+ const csrfToken = document
224
+ .querySelector('meta[name="csrf-token"]')
225
+ ?.getAttribute('content');
199
226
 
200
227
  // Fetch the item details before opening the modal
201
228
  // Laravel uses GET for retrieving data
202
- fetch(`${tableConfig.urls.get}${tableConfig.dataId}/${itemId}`, {
229
+ // Ensure the URL is properly formatted - it should end with a slash if we're appending an ID
230
+ const baseUrl = tableConfig.urls.get.endsWith('/')
231
+ ? tableConfig.urls.get
232
+ : `${tableConfig.urls.get}/`;
233
+
234
+ const fetchUrl = `${baseUrl}${itemId}`;
235
+ console.log('Fetching item details from:', fetchUrl);
236
+
237
+ fetch(fetchUrl, {
203
238
  method: 'GET',
204
239
  headers: {
205
240
  'Content-Type': 'application/json',
206
241
  'X-Requested-With': 'XMLHttpRequest',
207
242
  'X-CSRF-TOKEN': csrfToken,
208
- }
209
- })
210
- .then(response => {
211
- if (!response.ok) {
212
- throw new Error('Failed to fetch item details');
213
- }
214
- return response.json();
243
+ },
215
244
  })
216
- .then(itemData => {
217
- console.log('Fetched item data:', itemData);
218
- // Open the modal with the update form type and the fetched data
219
- // Use the update URL from the table configuration for the form
220
- const updatedFormSettings = {
221
- ...tableForm,
222
- update: {
223
- ...tableForm.update,
224
- submitUrl: tableConfig.urls.update + tableConfig.dataId + '/' + itemId,
225
- method: 'PUT' // Laravel uses PUT for updates
245
+ .then((response) => {
246
+ if (!response.ok) {
247
+ throw new Error(
248
+ `Failed to fetch item details: ${response.status} ${response.statusText}`
249
+ );
226
250
  }
227
- };
228
- modalOpen('update', itemId, updatedFormSettings, itemData);
229
- })
230
- .catch(error => {
231
- console.error('Error fetching item details:', error);
232
- toast.error('Failed to fetch item details: ' + error.message);
251
+ return response.text().then((text) => {
252
+ try {
253
+ return JSON.parse(text);
254
+ } catch (e) {
255
+ console.error('Invalid JSON response:', text);
256
+ throw new Error(
257
+ 'Invalid JSON response from server'
258
+ );
259
+ }
260
+ });
261
+ })
262
+ .then((itemData) => {
263
+ console.log('Fetched item data:', itemData);
264
+ // Open the modal with the update form type and the fetched data
265
+ // Use the update URL from the table configuration for the form
266
+ // Ensure the URL is properly formatted for the update
267
+ const baseUpdateUrl = tableConfig.urls.update.endsWith('/')
268
+ ? tableConfig.urls.update
269
+ : `${tableConfig.urls.update}/`;
270
+
271
+ const updatedFormSettings = {
272
+ ...tableForm,
273
+ update: {
274
+ ...tableForm.update,
275
+ submitUrl: `${baseUpdateUrl}${itemId}`,
276
+ method: 'PUT', // Laravel uses PUT for updates
277
+ },
278
+ };
279
+ modalOpen('update', itemId, updatedFormSettings, itemData);
280
+ })
281
+ .catch((error) => {
282
+ console.error('Error fetching item details:', error);
283
+ toast.error(
284
+ 'Failed to fetch item details: ' + error.message
285
+ );
233
286
 
234
- // Fall back to opening the modal with the row data
235
- modalOpen('update', itemId, tableForm, row);
236
- });
287
+ // Fall back to opening the modal with the row data
288
+ console.log('Falling back to row data:', row);
289
+
290
+ // Ensure we have a proper update URL even in the fallback case
291
+ let updateUrl;
292
+ if (tableConfig.urls && tableConfig.urls.update) {
293
+ const baseUpdateUrl = tableConfig.urls.update.endsWith(
294
+ '/'
295
+ )
296
+ ? tableConfig.urls.update
297
+ : `${tableConfig.urls.update}/`;
298
+ updateUrl = `${baseUpdateUrl}${itemId}`;
299
+ } else {
300
+ // Default URL if none is provided
301
+ updateUrl = `/ajax/quoteItems/${itemId}`;
302
+ }
303
+
304
+ console.log('Using fallback update URL:', updateUrl);
305
+
306
+ // Create updated form settings with the proper URL
307
+ const updatedFormSettings = {
308
+ ...tableForm,
309
+ update: {
310
+ ...tableForm.update,
311
+ submitUrl: updateUrl,
312
+ method: 'PUT', // Laravel uses PUT for updates
313
+ },
314
+ };
315
+
316
+ modalOpen('update', itemId, updatedFormSettings, row);
317
+ });
237
318
  } else {
319
+ // If there's no get URL, we still need to construct a proper update URL
320
+ console.log('No get URL found, using row data directly');
321
+
322
+ // Ensure we have a proper update URL
323
+ let updateUrl;
324
+ if (tableConfig.urls && tableConfig.urls.update) {
325
+ const baseUpdateUrl = tableConfig.urls.update.endsWith('/')
326
+ ? tableConfig.urls.update
327
+ : `${tableConfig.urls.update}/`;
328
+ updateUrl = `${baseUpdateUrl}${itemId}`;
329
+ } else {
330
+ // Default URL if none is provided
331
+ updateUrl = `/ajax/quoteItems/${itemId}`;
332
+ }
333
+
334
+ console.log('Using update URL:', updateUrl);
335
+
336
+ // Create updated form settings with the proper URL
337
+ const updatedFormSettings = {
338
+ ...tableForm,
339
+ update: {
340
+ ...tableForm.update,
341
+ submitUrl: updateUrl,
342
+ method: 'PUT', // Laravel uses PUT for updates
343
+ },
344
+ };
345
+
238
346
  // Open the modal with the update form type
239
- modalOpen('update', itemId, tableForm, row);
347
+ modalOpen('update', itemId, updatedFormSettings, row);
240
348
  }
241
349
  };
242
350
 
243
351
  const handleDeleteItem = (tableId, row, index) => {
244
352
  console.log('Delete item:', { tableId, row, index });
353
+ console.log('Available tables:', setting.tables);
354
+
355
+ // Ensure tableId is a string
356
+ const tableIdStr = String(tableId);
245
357
 
246
358
  // Find the table configuration
247
- const tableConfig = setting.tables.find(t => t.id === tableId);
248
- if (!tableConfig || !tableConfig.urls || !tableConfig.urls.delete) {
249
- console.error('Delete URL not found for table:', tableId);
250
- toast.error('Failed to delete item: Missing configuration');
359
+ const tableConfig = setting.tables.find(
360
+ (t) => String(t.id) === tableIdStr
361
+ );
362
+ if (!tableConfig) {
363
+ console.error('Table configuration not found for:', tableIdStr);
364
+ toast.error('Failed to delete item: Table configuration not found');
365
+ return;
366
+ }
367
+
368
+ if (!tableConfig.urls || !tableConfig.urls.delete) {
369
+ console.error('Delete URL not found for table:', tableIdStr);
370
+ toast.error(
371
+ 'Failed to delete item: Missing delete URL configuration'
372
+ );
373
+ return;
374
+ }
375
+
376
+ // Check if dataId is defined in the table configuration
377
+ if (!tableConfig.dataId) {
378
+ console.error(
379
+ 'dataId not defined in table configuration:',
380
+ tableConfig
381
+ );
382
+ toast.error('Failed to delete item: Missing dataId configuration');
251
383
  return;
252
384
  }
253
385
 
254
386
  // Get the item ID
255
387
  const itemId = row[tableConfig.dataId];
256
388
 
257
- // Show a confirmation dialog
258
- confirmDialog({
259
- title: 'Delete Item',
260
- message: 'Are you sure you want to delete this item?',
261
- confirmLabel: 'Delete',
262
- cancelLabel: 'Cancel',
263
- onConfirm: () => {
264
- // Get CSRF token
265
- const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
266
-
267
- // Laravel requires a special approach for DELETE requests
268
- // We need to include the _method parameter in the request body
269
- const formData = new FormData();
270
- formData.append('_method', 'DELETE');
271
-
272
- // Send the delete request to the server
273
- fetch(`${tableConfig.urls.delete}${tableConfig.dataId}/${itemId}`, {
274
- method: 'POST', // Actually sending as POST but Laravel will interpret as DELETE
275
- headers: {
276
- 'X-Requested-With': 'XMLHttpRequest',
277
- 'X-CSRF-TOKEN': csrfToken,
278
- },
279
- body: formData
280
- })
281
- .then(response => {
389
+ // Check if itemId exists
390
+ if (!itemId) {
391
+ console.error('Item ID not found in row data:', row);
392
+ toast.error('Failed to delete item: Missing item ID');
393
+ return;
394
+ }
395
+
396
+ console.log(
397
+ `Using dataId "${tableConfig.dataId}" to get item ID: ${itemId}`
398
+ );
399
+
400
+ // Define the delete function to be called when confirmed
401
+ const performDelete = () => {
402
+ console.log('Performing delete operation...');
403
+
404
+ // Show a loading toast
405
+ const toastId = toast.loading('Deleting item...');
406
+
407
+ // Get CSRF token
408
+ const csrfToken = document
409
+ .querySelector('meta[name="csrf-token"]')
410
+ ?.getAttribute('content');
411
+
412
+ // Laravel requires a special approach for DELETE requests
413
+ // We need to include the _method parameter in the request body
414
+ const formData = new FormData();
415
+ formData.append('_method', 'DELETE');
416
+
417
+ // Ensure the URL is properly formatted for the delete
418
+ const baseDeleteUrl = tableConfig.urls.delete.endsWith('/')
419
+ ? tableConfig.urls.delete
420
+ : `${tableConfig.urls.delete}/`;
421
+
422
+ const deleteUrl = `${baseDeleteUrl}${itemId}`;
423
+ console.log('Deleting item from:', deleteUrl);
424
+
425
+ // Send the delete request to the server
426
+ fetch(deleteUrl, {
427
+ method: 'POST', // Actually sending as POST but Laravel will interpret as DELETE
428
+ headers: {
429
+ 'X-Requested-With': 'XMLHttpRequest',
430
+ 'X-CSRF-TOKEN': csrfToken,
431
+ },
432
+ body: formData,
433
+ })
434
+ .then((response) => {
282
435
  if (!response.ok) {
283
436
  throw new Error('Failed to delete item');
284
437
  }
285
438
  return response.json();
286
439
  })
287
- .then(responseData => {
440
+ .then((responseData) => {
288
441
  console.log('Item deleted successfully:', responseData);
289
442
 
290
443
  // Create a copy of the data
291
444
  const newData = { ...data };
292
445
 
293
446
  // Remove the item from the specified table
294
- newData[tableId] = newData[tableId].filter(item => item[tableConfig.dataId] !== itemId);
447
+ newData[tableId] = newData[tableId].filter(
448
+ (item) => item[tableConfig.dataId] !== itemId
449
+ );
295
450
 
296
451
  // Update the state
297
452
  setData(newData);
298
453
 
299
- // Show a success toast
300
- toast.success('Item deleted successfully');
454
+ // Update toast
455
+ toast.update(toastId, {
456
+ render: 'Item deleted successfully',
457
+ type: 'success',
458
+ isLoading: false,
459
+ autoClose: 3000,
460
+ closeButton: true,
461
+ });
301
462
  })
302
- .catch(error => {
463
+ .catch((error) => {
303
464
  console.error('Error deleting item:', error);
304
- toast.error('Failed to delete item: ' + error.message);
465
+
466
+ // Update toast
467
+ toast.update(toastId, {
468
+ render: 'Failed to delete item: ' + error.message,
469
+ type: 'error',
470
+ isLoading: false,
471
+ autoClose: 5000,
472
+ closeButton: true,
473
+ });
305
474
  });
306
- }
475
+ };
476
+
477
+ // Show a confirmation dialog
478
+ showConfirmDialog({
479
+ title: 'Delete Item',
480
+ message: 'Are you sure you want to delete this item?',
481
+ confirmLabel: 'Delete',
482
+ cancelLabel: 'Cancel',
483
+ data: row,
484
+ onConfirm: performDelete,
485
+ onCancel: () => {
486
+ console.log('Delete operation cancelled');
487
+ toast.info('Delete operation cancelled');
488
+ },
307
489
  });
308
490
  };
309
491
 
@@ -311,26 +493,40 @@ function GenericQuote({ logo, setting, urlParam }) {
311
493
  console.log('Add item to table:', tableId);
312
494
 
313
495
  // Find the table configuration
314
- const tableConfig = setting.tables.find(t => t.id === tableId);
496
+ const tableConfig = setting.tables.find((t) => t.id === tableId);
315
497
  if (!tableConfig) {
316
498
  console.error('Table configuration not found for:', tableId);
317
499
  toast.error('Failed to add item: Missing configuration');
318
500
  return;
319
501
  }
320
502
 
321
- // Find the form settings for the table
322
- const tableForm = tableConfig.form || form;
503
+ // Find the addItem action to get its form settings
504
+ const addItemAction = actions.find((action) => action.id === 'addItem');
505
+ // Use the addItem action's settings if available, otherwise fall back to table form or default form
506
+ const tableForm = addItemAction?.setting || tableConfig.form || form;
507
+
508
+ // Update the form settings to include the proper submitUrl
509
+ // If there's a submitUrl in the form settings, use it with proper formatting
510
+ let submitUrl;
511
+ if (tableForm.create?.submitUrl) {
512
+ const baseUrl = tableForm.create.submitUrl.endsWith('/')
513
+ ? tableForm.create.submitUrl
514
+ : `${tableForm.create.submitUrl}/`;
515
+ submitUrl = `${baseUrl}${routeParams[urlParam]}`;
516
+ } else {
517
+ // Default URL if none is provided
518
+ submitUrl = `/ajax/quoteItems/${routeParams[urlParam]}`;
519
+ }
520
+
521
+ console.log('Add item submitUrl:', submitUrl);
323
522
 
324
- // Update the form settings to include the dataId in the submitUrl
325
523
  const updatedFormSettings = {
326
524
  ...tableForm,
327
525
  create: {
328
526
  ...tableForm.create,
329
- submitUrl: tableForm.create?.submitUrl
330
- ? `${tableForm.create.submitUrl}${tableConfig.dataId}`
331
- : `/ajax/quoteItems/${tableConfig.dataId}`,
332
- method: 'POST' // Laravel uses POST for creating new items
333
- }
527
+ submitUrl: submitUrl,
528
+ method: 'POST', // Laravel uses POST for creating new items
529
+ },
334
530
  };
335
531
 
336
532
  // Open the modal with the create form type and updated form settings
@@ -423,9 +619,9 @@ function GenericQuote({ logo, setting, urlParam }) {
423
619
  const requestBody = {
424
620
  view: action.view,
425
621
  data: {
426
- quote: data, // Pass the current quote data
622
+ [action.dataKey]: data, // Pass the current quote data
427
623
  },
428
- filename: `quote-${data.id}.pdf`,
624
+ filename: `${action.filenamePrefix}-${data.id}.pdf`,
429
625
  paper: action.paper || 'a4',
430
626
  orientation: action.orientation || 'portrait',
431
627
  download: action.download !== false, // Default to true if not specified
@@ -495,8 +691,13 @@ function GenericQuote({ logo, setting, urlParam }) {
495
691
  // Handle other actions based on their id if no specific type is defined
496
692
  switch (action.id) {
497
693
  case 'addItem':
498
- // Handle add item action
694
+ // Handle add item action - find the first table and add an item to it
499
695
  console.log('Add item clicked');
696
+ if (setting.tables && setting.tables.length > 0) {
697
+ handleAddItem(setting.tables[0].id);
698
+ } else {
699
+ toast.error('No tables found to add item to');
700
+ }
500
701
  break;
501
702
  case 'editQuote':
502
703
  // If no type is specified but id is editQuote, default to opening the edit modal
@@ -515,14 +716,14 @@ function GenericQuote({ logo, setting, urlParam }) {
515
716
 
516
717
  // Default PDF generation with standard settings
517
718
  const requestBody = {
518
- view: 'quote.pdf', // Default view
719
+ view: action.view,
519
720
  data: {
520
- quote: data, // Pass the current quote data
721
+ [action.dataKey]: data, // Pass the current quote data
521
722
  },
522
- filename: `quote-${data.id}.pdf`,
523
- paper: 'a4',
524
- orientation: 'portrait',
525
- download: true,
723
+ filename: `${action.filenamePrefix}-${data.id}.pdf`,
724
+ paper: action.paper || 'a4',
725
+ orientation: action.orientation || 'portrait',
726
+ download: action.download !== false, // Default to true if not specified
526
727
  };
527
728
 
528
729
  // Filename for the downloaded PDF
@@ -603,7 +804,7 @@ function GenericQuote({ logo, setting, urlParam }) {
603
804
  <div className={styles.quoteTopSection}>
604
805
  <div className={styles.quoteIdentifier}>
605
806
  <h1 className={styles.quoteTitle}>
606
- Quote
807
+ {page?.title || 'Quote'}
607
808
  <span className={styles.quoteNumber}>
608
809
  #{data.id || ''}
609
810
  </span>
@@ -632,11 +833,21 @@ function GenericQuote({ logo, setting, urlParam }) {
632
833
  ]
633
834
  }
634
835
  >
635
- {data.status ? data.status.name : ''}
636
- {data.task && data.task.ticket_id && (
637
- <span className={styles.ticketId}>
638
- - #{data.task.ticket_id}
639
- </span>
836
+ {data.status ? (
837
+ <>
838
+ {data.status.name}
839
+ {data.task?.ticket_id && (
840
+ <span className={styles.ticketId}>
841
+ - #{data.task.ticket_id}
842
+ </span>
843
+ )}
844
+ </>
845
+ ) : (
846
+ data.task?.ticket_id && (
847
+ <span className={styles.ticketId}>
848
+ Ticket #{data.task.ticket_id}
849
+ </span>
850
+ )
640
851
  )}
641
852
  </span>
642
853
  </div>
@@ -662,7 +873,7 @@ function GenericQuote({ logo, setting, urlParam }) {
662
873
  </div>
663
874
  <div className={styles.quoteTitleContainer}>
664
875
  <h2 className={styles.quoteBigNumber}>
665
- Quote #{data.id || ''}
876
+ {page?.title || 'Quote'} #{data.id || ''}
666
877
  </h2>
667
878
  <div className={styles.quoteDate}>
668
879
  <strong>Date:</strong>{' '}
@@ -759,6 +970,41 @@ function GenericQuote({ logo, setting, urlParam }) {
759
970
  );
760
971
  })()}
761
972
  </>
973
+ ) : data.supplier ? (
974
+ <>
975
+ {data.supplier.name}
976
+ <br />
977
+ {data.supplier.address1 && (
978
+ <>
979
+ {data.supplier.address1}
980
+ <br />
981
+ </>
982
+ )}
983
+ {data.supplier.address2 && (
984
+ <>
985
+ {data.supplier.address2}
986
+ <br />
987
+ </>
988
+ )}
989
+ {data.supplier.suburb &&
990
+ data.supplier.postcode && (
991
+ <>
992
+ {data.supplier.suburb}
993
+ {data.supplier.state_id
994
+ ? `, ${data.supplier.state_id}`
995
+ : ''}
996
+ {', '}
997
+ {data.supplier.postcode}
998
+ <br />
999
+ </>
1000
+ )}
1001
+ {data.supplier.abn && (
1002
+ <>
1003
+ ABN: {data.supplier.abn}
1004
+ <br />
1005
+ </>
1006
+ )}
1007
+ </>
762
1008
  ) : (
763
1009
  ''
764
1010
  )}
@@ -767,7 +1013,7 @@ function GenericQuote({ logo, setting, urlParam }) {
767
1013
  <div className={styles.quoteParty}>
768
1014
  <h3 className={styles.partyTitle}>Quote From:</h3>
769
1015
  <div className={styles.partyDetails}>
770
- Visns Studio
1016
+ Omnia Global
771
1017
  <br />
772
1018
  660C Newcastle Street
773
1019
  <br />
@@ -775,9 +1021,9 @@ function GenericQuote({ logo, setting, urlParam }) {
775
1021
  <br />
776
1022
  08 6383 6600
777
1023
  <br />
778
- team@visns.studio
1024
+ team@omniaglobal.com.au
779
1025
  <br />
780
- ABN: 41 994 317 718
1026
+ ACN: 674 383 987
781
1027
  </div>
782
1028
  </div>
783
1029
  </div>
@@ -843,7 +1089,9 @@ function GenericQuote({ logo, setting, urlParam }) {
843
1089
  </h2>
844
1090
  <button
845
1091
  className={styles.addItemButton}
846
- onClick={() => handleAddItem(table.id)}
1092
+ onClick={() =>
1093
+ handleAddItem(table.id)
1094
+ }
847
1095
  >
848
1096
  + Add Item
849
1097
  </button>
@@ -854,7 +1102,11 @@ function GenericQuote({ logo, setting, urlParam }) {
854
1102
  >
855
1103
  <thead>
856
1104
  <tr>
857
- <th className={styles.dragHandleHeader}></th>
1105
+ <th
1106
+ className={
1107
+ styles.dragHandleHeader
1108
+ }
1109
+ ></th>
858
1110
  {table.columns.map((column) => (
859
1111
  <th
860
1112
  key={`${table.id}-column-${column.id}`}
@@ -871,22 +1123,62 @@ function GenericQuote({ logo, setting, urlParam }) {
871
1123
  : column.label}
872
1124
  </th>
873
1125
  ))}
874
- <th className={styles.actionHeader}>Actions</th>
1126
+ <th
1127
+ className={
1128
+ styles.actionHeader
1129
+ }
1130
+ >
1131
+ Actions
1132
+ </th>
875
1133
  </tr>
876
1134
  </thead>
877
1135
  <SortableTableBody
878
1136
  items={data[table.id]}
879
1137
  columns={table.columns}
880
- onSortEnd={({oldIndex, newIndex}) => handleSortEnd(table.id, oldIndex, newIndex)}
881
- onEdit={(row, index) => handleEditItem(table.id, row, index)}
882
- onDelete={(row, index) => handleDeleteItem(table.id, row, index)}
1138
+ onSortEnd={({
1139
+ oldIndex,
1140
+ newIndex,
1141
+ }) =>
1142
+ handleSortEnd(
1143
+ table.id,
1144
+ oldIndex,
1145
+ newIndex
1146
+ )
1147
+ }
1148
+ onEdit={(row, index) => {
1149
+ console.log(
1150
+ 'Edit callback with index:',
1151
+ index
1152
+ );
1153
+ handleEditItem(
1154
+ table.id,
1155
+ row,
1156
+ index
1157
+ );
1158
+ }}
1159
+ onDelete={(row, index) => {
1160
+ console.log(
1161
+ 'Delete callback from SortableTableBody:',
1162
+ {
1163
+ tableId: table.id,
1164
+ row,
1165
+ index,
1166
+ }
1167
+ );
1168
+ handleDeleteItem(
1169
+ table.id,
1170
+ row,
1171
+ index
1172
+ );
1173
+ }}
883
1174
  useDragHandle
884
1175
  helperClass="sortableHelper"
885
1176
  distance={5}
886
1177
  />
887
1178
  <tfoot>
888
1179
  <tr className={styles.subtotalRow}>
889
- <td></td> {/* Empty cell for drag handle */}
1180
+ <td></td>{' '}
1181
+ {/* Empty cell for drag handle */}
890
1182
  <td
891
1183
  colSpan={
892
1184
  table.columns.length - 1
@@ -904,7 +1196,8 @@ function GenericQuote({ logo, setting, urlParam }) {
904
1196
  >
905
1197
  {formatCurrency(subtotal)}
906
1198
  </td>
907
- <td></td> {/* Empty cell for action buttons */}
1199
+ <td></td>{' '}
1200
+ {/* Empty cell for action buttons */}
908
1201
  </tr>
909
1202
  </tfoot>
910
1203
  </table>