@visns-studio/visns-components 5.12.2 → 5.12.4

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.
@@ -71,6 +71,7 @@ import GenericEditableTable from './GenericEditableTable';
71
71
  import MergeEntity from '../MergeEntity';
72
72
  import MultiSelect from '../MultiSelect';
73
73
  import QrCode from '../QrCode';
74
+ import StagePopupModal from './StagePopupModal';
74
75
  import Table from '../DataGrid';
75
76
  import TableFilter from '../TableFilter';
76
77
 
@@ -157,6 +158,10 @@ function GenericDetail({
157
158
  const [total, setTotal] = useState(0);
158
159
  const [windowHeight, setWindowHeight] = useState(window.innerHeight);
159
160
 
161
+ /** Stage Popup States */
162
+ const [showStagePopup, setShowStagePopup] = useState(false);
163
+ const [stagePopupData, setStagePopupData] = useState(null);
164
+
160
165
  /** Ocr States */
161
166
  const [ocrRowsSelected, setOcrRowsSelected] = useState('');
162
167
 
@@ -297,6 +302,45 @@ function GenericDetail({
297
302
  };
298
303
 
299
304
  const handleStageUpdate = async (stage, type) => {
305
+ try {
306
+ // Check if stage has popup configuration
307
+ if (stage.popup?.enabled) {
308
+ setStagePopupData({ stage, type });
309
+ setShowStagePopup(true);
310
+ return;
311
+ }
312
+
313
+ // If no popup, proceed with normal stage update
314
+ await performStageUpdate(stage, type);
315
+ } catch (error) {
316
+ console.error(error);
317
+ }
318
+ };
319
+
320
+ const handleStagePopupConfirm = async (popupFormData) => {
321
+ try {
322
+ setShowStagePopup(false);
323
+
324
+ if (!stagePopupData) return;
325
+
326
+ const { stage, type } = stagePopupData;
327
+
328
+ // Proceed with normal stage update, but include popup form data
329
+ await performStageUpdate(stage, type, popupFormData);
330
+
331
+ setStagePopupData(null);
332
+ } catch (error) {
333
+ console.error('Stage popup confirm error:', error);
334
+ toast.error('Failed to update stage');
335
+ }
336
+ };
337
+
338
+ const handleStagePopupCancel = () => {
339
+ setShowStagePopup(false);
340
+ setStagePopupData(null);
341
+ };
342
+
343
+ const performStageUpdate = async (stage, type, additionalData = {}) => {
300
344
  try {
301
345
  const activeTab = subnav.find((s) => s.show === true);
302
346
 
@@ -305,7 +349,102 @@ function GenericDetail({
305
349
 
306
350
  if (type === 'view') {
307
351
  setActiveStage(stage.id);
352
+ } else if (type === 'stageCounter') {
353
+ // Handle new stageCounter functionality
354
+ console.log('performStageUpdate called with stageCounter type:', { stage, data, stages });
355
+
356
+ const stageConfig = stages.stageConfig || activeTabConfig.stages?.stageConfig;
357
+ if (!stageConfig?.toggleable || !stageConfig?.toggleConfig) {
358
+ console.warn('Stage is not configured for toggling', { stageConfig });
359
+ return;
360
+ }
361
+
362
+ // Get current stage data
363
+ const stageDataArray = data[stageConfig.key] || [];
364
+ const currentStage = stageDataArray.find(s => s.id === stage.id);
365
+
366
+ if (!currentStage) {
367
+ console.warn('Stage not found in data');
368
+ return;
369
+ }
370
+
371
+ // Determine current and target status
372
+ const currentStatus = currentStage[stageConfig.toggleConfig.statusField];
373
+ const isActive = currentStatus === stageConfig.toggleConfig.activeStatusValue;
374
+ const targetStatus = isActive
375
+ ? stageConfig.toggleConfig.inactiveStatusValue
376
+ : stageConfig.toggleConfig.activeStatusValue;
377
+
378
+ // Build API URL
379
+ let url = stageConfig.toggleConfig.updateUrl;
380
+ url = url.replace('{id}', routeParams[stages.urlParam]);
381
+ url = url.replace('{stage_id}', stage.id);
382
+
383
+ // Make API call with additional data from popup
384
+ const res = await CustomFetch(
385
+ url,
386
+ stageConfig.toggleConfig.updateMethod || 'PUT',
387
+ {
388
+ [stageConfig.toggleConfig.updateKey]: targetStatus,
389
+ ...additionalData
390
+ }
391
+ );
392
+
393
+ if (res.data.error === '') {
394
+ handleReload();
395
+ const actionText = isActive ? 'disabled' : 'enabled';
396
+ toast.success(`Stage "${stage.label}" ${actionText} successfully`);
397
+ } else {
398
+ toast.error(res.data.error);
399
+ }
400
+ } else if (type === 'legacy_toggle') {
401
+ // Handle legacy toggle functionality - toggle between current stage and previous
402
+ console.log('performStageUpdate called with legacy_toggle type:', { stage, data, stages });
403
+
404
+ if (
405
+ stages &&
406
+ stages.url &&
407
+ stages.url !== '' &&
408
+ stages.key &&
409
+ stages.key !== '' &&
410
+ stages.urlParam &&
411
+ stages.urlParam !== ''
412
+ ) {
413
+ const currentStatus = data[stages.key];
414
+ let targetStatus;
415
+
416
+ // If clicking on the current stage (completed), move back one step
417
+ // If clicking on a future stage, move to that stage
418
+ if (currentStatus >= stage.id) {
419
+ // Stage is completed, move back one step (or to 0 if clicking stage 1)
420
+ targetStatus = stage.id > 1 ? stage.id - 1 : 0;
421
+ } else {
422
+ // Stage is not completed, move to this stage
423
+ targetStatus = stage.id;
424
+ }
425
+
426
+ const url = `${stages.url}/${routeParams[stages.urlParam]}`;
427
+
428
+ // Include additional data from popup
429
+ const res = await CustomFetch(
430
+ url,
431
+ stages.method || 'PUT',
432
+ {
433
+ [stages.key]: targetStatus,
434
+ ...additionalData
435
+ }
436
+ );
437
+
438
+ if (res.data.error === '') {
439
+ handleReload();
440
+ const actionText = currentStatus >= stage.id ? 'moved back from' : 'progressed to';
441
+ toast.success(`Stage ${actionText} "${stage.label}" successfully`);
442
+ } else {
443
+ toast.error(res.data.error);
444
+ }
445
+ }
308
446
  } else {
447
+ // Handle legacy stage functionality
309
448
  if (
310
449
  activeTabConfig.stages &&
311
450
  activeTabConfig.stages.url &&
@@ -319,6 +458,7 @@ function GenericDetail({
319
458
 
320
459
  const res = await CustomFetch(url, 'POST', {
321
460
  [activeTabConfig.stages.key]: stage.id,
461
+ ...additionalData
322
462
  });
323
463
 
324
464
  if (res.data.error === '') {
@@ -343,6 +483,7 @@ function GenericDetail({
343
483
  stages.method || 'POST',
344
484
  {
345
485
  [stages.key]: stage.id,
486
+ ...additionalData
346
487
  }
347
488
  );
348
489
 
@@ -2878,8 +3019,46 @@ function GenericDetail({
2878
3019
  <div className={styles.oppstatus}>
2879
3020
  <ul className={styles.opppath}>
2880
3021
  {stages.data.map((stage) => {
2881
- const isStageComplete =
2882
- data[stages.key] >= stage.id;
3022
+ let isStageComplete = false;
3023
+
3024
+ if (stages.type === 'stageCounter') {
3025
+ // For stageCounter, check individual stage status
3026
+ const stageConfig = stages.stageConfig;
3027
+ if (stageConfig && data[stageConfig.key]) {
3028
+ const stageDataArray = data[stageConfig.key];
3029
+ const currentStage = stageDataArray.find(s => s.id === stage.id);
3030
+ if (currentStage && stageConfig.toggleConfig) {
3031
+ const currentStatus = currentStage[stageConfig.toggleConfig.statusField];
3032
+ isStageComplete = currentStatus === stageConfig.toggleConfig.activeStatusValue;
3033
+
3034
+ // Debug logging
3035
+ console.log('Stage completion check:', {
3036
+ stageId: stage.id,
3037
+ stageLabel: stage.label,
3038
+ currentStatus,
3039
+ activeStatusValue: stageConfig.toggleConfig.activeStatusValue,
3040
+ isStageComplete
3041
+ });
3042
+ }
3043
+ }
3044
+ } else {
3045
+ // Legacy stage completion check
3046
+ isStageComplete = data[stages.key] >= stage.id;
3047
+ }
3048
+
3049
+ // Get color for stageCounter type
3050
+ let stageColor = null;
3051
+ if (stages.type === 'stageCounter' && stages.stageConfig?.stageColour) {
3052
+ const stageDataArray = data[stages.stageConfig.key] || [];
3053
+ const currentStage = stageDataArray.find(s => s.id === stage.id);
3054
+ if (currentStage) {
3055
+ const currentStatus = currentStage[stages.stageConfig.stageColour.key];
3056
+ const colorOption = stages.stageConfig.stageColour.options?.find(
3057
+ opt => opt.id === currentStatus
3058
+ );
3059
+ stageColor = colorOption?.colour;
3060
+ }
3061
+ }
2883
3062
 
2884
3063
  return (
2885
3064
  <li
@@ -2889,6 +3068,11 @@ function GenericDetail({
2889
3068
  ? `${styles['opp-complete']}`
2890
3069
  : ''
2891
3070
  }
3071
+ style={stageColor ? {
3072
+ borderColor: stageColor,
3073
+ color: isStageComplete ? '#fff' : stageColor,
3074
+ backgroundColor: isStageComplete ? stageColor : 'transparent'
3075
+ } : {}}
2892
3076
  onClick={(e) => {
2893
3077
  e.preventDefault();
2894
3078
 
@@ -2898,12 +3082,15 @@ function GenericDetail({
2898
3082
  );
2899
3083
  }}
2900
3084
  >
2901
- <a>
3085
+ <a style={stageColor ? {
3086
+ color: isStageComplete ? '#fff' : stageColor
3087
+ } : {}}>
2902
3088
  {stage.label}
2903
3089
  {isStageComplete && (
2904
3090
  <CircleCheck
2905
3091
  strokeWidth={2}
2906
3092
  size={16}
3093
+ color={stageColor ? '#fff' : undefined}
2907
3094
  />
2908
3095
  )}
2909
3096
  </a>
@@ -2970,6 +3157,16 @@ function GenericDetail({
2970
3157
  </div>
2971
3158
  </div>
2972
3159
  </Popup>
3160
+
3161
+ <StagePopupModal
3162
+ show={showStagePopup}
3163
+ stage={stagePopupData?.stage}
3164
+ popupConfig={stagePopupData?.stage?.popup}
3165
+ onConfirm={handleStagePopupConfirm}
3166
+ onCancel={handleStagePopupCancel}
3167
+ entityData={data}
3168
+ routeParams={routeParams}
3169
+ />
2973
3170
  </>
2974
3171
  );
2975
3172
  }
@@ -1,11 +1,6 @@
1
1
  import '../styles/global.css';
2
2
 
3
- import React, {
4
- useCallback,
5
- useEffect,
6
- useRef,
7
- useState,
8
- } from 'react';
3
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
9
4
  import { useParams, Outlet } from 'react-router-dom';
10
5
  import { toast } from 'react-toastify';
11
6
  import { saveAs } from 'file-saver';
@@ -223,7 +218,6 @@ function useConfig(setting, tabs, filters, setRowsSelected, tableSetting) {
223
218
  return { config, setConfig, subnav, setSubnav };
224
219
  }
225
220
 
226
-
227
221
  function GenericIndex({
228
222
  layout = 'full',
229
223
  setting,
@@ -494,26 +488,38 @@ function GenericIndex({
494
488
  let payload = {};
495
489
  if (config.export.payload) {
496
490
  payload = { ...config.export.payload };
497
-
491
+
498
492
  // Process special values in the payload
499
- Object.keys(payload).forEach(key => {
493
+ Object.keys(payload).forEach((key) => {
500
494
  if (payload[key] === 'currentActive()') {
501
495
  // Get the current active filter value
502
- const activeFilter = subnav?.find(filter => {
496
+ const activeFilter = subnav?.find((filter) => {
503
497
  if (filter.isParent) {
504
- return filter.children && filter.children.some(child => child.show);
498
+ return (
499
+ filter.children &&
500
+ filter.children.some((child) => child.show)
501
+ );
505
502
  }
506
503
  return filter.show === true;
507
504
  });
508
505
 
509
506
  if (activeFilter) {
510
- if (activeFilter.isParent && activeFilter.children && activeFilter.children.length > 0) {
507
+ if (
508
+ activeFilter.isParent &&
509
+ activeFilter.children &&
510
+ activeFilter.children.length > 0
511
+ ) {
511
512
  // If it's a parent filter, get the active child's value
512
- const activeChild = activeFilter.children.find(child => child.show);
513
- payload[key] = activeChild ? activeChild.value || activeChild.id : '';
513
+ const activeChild = activeFilter.children.find(
514
+ (child) => child.show
515
+ );
516
+ payload[key] = activeChild
517
+ ? activeChild.value || activeChild.id
518
+ : '';
514
519
  } else {
515
520
  // For non-parent filters, use the filter's value or id
516
- payload[key] = activeFilter.value || activeFilter.id;
521
+ payload[key] =
522
+ activeFilter.value || activeFilter.id;
517
523
  }
518
524
  } else {
519
525
  // If no active filter is found, set to empty string
@@ -571,7 +577,7 @@ function GenericIndex({
571
577
  {...configWithStyle}
572
578
  ref={gridRef}
573
579
  hoverColor={configWithStyle.style?.hoverColor}
574
- gridHeight={windowHeight * 0.65}
580
+ gridHeight={windowHeight * 0.8}
575
581
  setConfig={setConfig}
576
582
  setTableData={setTableData}
577
583
  setTotal={setTotal}
@@ -606,11 +612,18 @@ function GenericIndex({
606
612
  const renderContentBasedOnType = useCallback(() => {
607
613
  switch (config.type) {
608
614
  case 'grid':
609
- return <GenericGrid config={config} userProfile={userProfile} />;
615
+ return (
616
+ <GenericGrid config={config} userProfile={userProfile} />
617
+ );
610
618
  case 'table':
611
619
  return renderTable();
612
620
  case 'report':
613
- return <GenericReportForm config={config} userProfile={userProfile} />;
621
+ return (
622
+ <GenericReportForm
623
+ config={config}
624
+ userProfile={userProfile}
625
+ />
626
+ );
614
627
  default:
615
628
  return renderTable(); // Fallback to table if no type is specified or recognized
616
629
  }