@@ -22,9 +22,15 @@ import {
22
22
Stack ,
23
23
Divider ,
24
24
Collapse ,
25
+ ToggleButton ,
26
+ ToggleButtonGroup ,
27
+ List ,
28
+ ListItem ,
29
+ ListItemText ,
30
+ ListItemSecondaryAction ,
25
31
} from "@mui/material" ;
26
32
import { Grid } from "@mui/system" ;
27
- import { Add , Sort , Clear , FilterList , ExpandMore , ExpandLess } from "@mui/icons-material" ;
33
+ import { Add , Sort , Clear , FilterList , ExpandMore , ExpandLess , ViewModule , ViewList } from "@mui/icons-material" ;
28
34
import { useState , useCallback , useMemo , memo , useEffect } from "react" ;
29
35
import { debounce } from "lodash" ;
30
36
import { Virtuoso } from "react-virtuoso" ;
@@ -341,6 +347,193 @@ const VirtualizedStandardGrid = memo(({ items, renderItem }) => {
341
347
342
348
VirtualizedStandardGrid . displayName = "VirtualizedStandardGrid" ;
343
349
350
+ // Compact List View component for standards
351
+ const CompactStandardList = memo ( ( { items, selectedStandards, handleToggleSingleStandard, handleAddClick, isButtonDisabled } ) => {
352
+ return (
353
+ < List sx = { { width : '100%' , bgcolor : 'background.paper' } } >
354
+ { items . map ( ( { standard, category } ) => {
355
+ const isSelected = ! ! selectedStandards [ standard . name ] ;
356
+
357
+ const isNewStandard = ( dateAdded ) => {
358
+ if ( ! dateAdded ) return false ;
359
+ const currentDate = new Date ( ) ;
360
+ const addedDate = new Date ( dateAdded ) ;
361
+ return differenceInDays ( currentDate , addedDate ) <= 30 ;
362
+ } ;
363
+
364
+ const handleToggle = ( ) => {
365
+ handleToggleSingleStandard ( standard . name ) ;
366
+ } ;
367
+
368
+ return (
369
+ < ListItem
370
+ key = { standard . name }
371
+ sx = { {
372
+ border : '1px solid' ,
373
+ borderColor : 'divider' ,
374
+ borderRadius : 1 ,
375
+ mb : 1 ,
376
+ bgcolor : 'background.paper' ,
377
+ '&:hover' : {
378
+ bgcolor : 'action.hover' ,
379
+ } ,
380
+ ...( isNewStandard ( standard . addedDate ) && {
381
+ borderColor : 'success.main' ,
382
+ borderWidth : '2px' ,
383
+ } )
384
+ } }
385
+ >
386
+ < ListItemText
387
+ primary = {
388
+ < Box sx = { { display : 'flex' , alignItems : 'center' , gap : 1 , mb : 1 } } >
389
+ < Typography variant = "subtitle1" sx = { { fontWeight : 'medium' } } >
390
+ { standard . label }
391
+ </ Typography >
392
+ { isNewStandard ( standard . addedDate ) && (
393
+ < Chip
394
+ label = "New"
395
+ size = "small"
396
+ color = "success"
397
+ sx = { { fontSize : '0.7rem' , height : 20 , fontWeight : 'bold' } }
398
+ />
399
+ ) }
400
+ < Chip label = { category } size = "small" color = "primary" />
401
+ < Chip
402
+ label = { standard . impact }
403
+ size = "small"
404
+ color = {
405
+ standard . impact === "High Impact"
406
+ ? "error"
407
+ : standard . impact === "Medium Impact"
408
+ ? "warning"
409
+ : "info"
410
+ }
411
+ />
412
+ </ Box >
413
+ }
414
+ secondary = {
415
+ < Box >
416
+ { standard . helpText && (
417
+ < Box
418
+ sx = { {
419
+ mb : 1 ,
420
+ "& a" : {
421
+ color : ( theme ) => theme . palette . primary . main ,
422
+ textDecoration : "underline" ,
423
+ "&:hover" : {
424
+ textDecoration : "none" ,
425
+ } ,
426
+ } ,
427
+ color : "text.secondary" ,
428
+ fontSize : "0.875rem" ,
429
+ lineHeight : 1.43 ,
430
+ } }
431
+ >
432
+ < ReactMarkdown
433
+ components = { {
434
+ a : ( { href, children, ...props } ) => (
435
+ < a
436
+ href = { href }
437
+ target = "_blank"
438
+ rel = "noopener noreferrer"
439
+ { ...props }
440
+ >
441
+ { children }
442
+ </ a >
443
+ ) ,
444
+ p : ( { children } ) => (
445
+ < Typography
446
+ variant = "body2"
447
+ color = "text.secondary"
448
+ sx = { {
449
+ display : '-webkit-box' ,
450
+ WebkitLineClamp : 2 ,
451
+ WebkitBoxOrient : 'vertical' ,
452
+ overflow : 'hidden' ,
453
+ textOverflow : 'ellipsis' ,
454
+ mb : 0 ,
455
+ } }
456
+ >
457
+ { children }
458
+ </ Typography >
459
+ ) ,
460
+ } }
461
+ >
462
+ { standard . helpText }
463
+ </ ReactMarkdown >
464
+ </ Box >
465
+ ) }
466
+ < Box sx = { { display : 'flex' , flexWrap : 'wrap' , gap : 1 , alignItems : 'center' } } >
467
+ { standard . tag ?. filter ( ( tag ) => ! tag . toLowerCase ( ) . includes ( "impact" ) ) . length > 0 && (
468
+ < Box sx = { { display : 'flex' , flexWrap : 'wrap' , gap : 0.5 } } >
469
+ { standard . tag
470
+ . filter ( ( tag ) => ! tag . toLowerCase ( ) . includes ( "impact" ) )
471
+ . slice ( 0 , 3 ) // Show only first 3 tags to save space
472
+ . map ( ( tag , idx ) => (
473
+ < Chip
474
+ key = { idx }
475
+ label = { tag }
476
+ size = "small"
477
+ color = "info"
478
+ variant = "outlined"
479
+ sx = { { fontSize : '0.7rem' , height : 20 } }
480
+ />
481
+ ) ) }
482
+ { standard . tag . filter ( ( tag ) => ! tag . toLowerCase ( ) . includes ( "impact" ) ) . length > 3 && (
483
+ < Typography variant = "caption" color = "text.secondary" >
484
+ +{ standard . tag . filter ( ( tag ) => ! tag . toLowerCase ( ) . includes ( "impact" ) ) . length - 3 } more
485
+ </ Typography >
486
+ ) }
487
+ </ Box >
488
+ ) }
489
+ { standard . recommendedBy ?. length > 0 && (
490
+ < Typography variant = "caption" color = "text.secondary" >
491
+ • Recommended by: { standard . recommendedBy . join ( ", " ) }
492
+ </ Typography >
493
+ ) }
494
+ { standard . addedDate && (
495
+ < Typography variant = "caption" color = "text.secondary" >
496
+ • Added: { standard . addedDate }
497
+ </ Typography >
498
+ ) }
499
+ </ Box >
500
+ </ Box >
501
+ }
502
+ />
503
+ < ListItemSecondaryAction >
504
+ { standard . multiple ? (
505
+ < IconButton
506
+ color = "primary"
507
+ disabled = { isButtonDisabled }
508
+ onClick = { ( ) => handleAddClick ( standard . name ) }
509
+ sx = { { mr : 1 } }
510
+ >
511
+ < Add />
512
+ </ IconButton >
513
+ ) : (
514
+ < FormControlLabel
515
+ control = {
516
+ < Switch
517
+ checked = { isSelected }
518
+ onChange = { handleToggle }
519
+ color = "primary"
520
+ size = "small"
521
+ />
522
+ }
523
+ label = ""
524
+ sx = { { mr : 1 } }
525
+ />
526
+ ) }
527
+ </ ListItemSecondaryAction >
528
+ </ ListItem >
529
+ ) ;
530
+ } ) }
531
+ </ List >
532
+ ) ;
533
+ } ) ;
534
+
535
+ CompactStandardList . displayName = "CompactStandardList" ;
536
+
344
537
const CippStandardDialog = ( {
345
538
dialogOpen,
346
539
handleCloseDialog,
@@ -354,6 +547,7 @@ const CippStandardDialog = ({
354
547
const [ isButtonDisabled , setButtonDisabled ] = useState ( false ) ;
355
548
const [ localSearchQuery , setLocalSearchQuery ] = useState ( "" ) ;
356
549
const [ isInitialLoading , setIsInitialLoading ] = useState ( true ) ;
550
+ const [ viewMode , setViewMode ] = useState ( "card" ) ; // "card" or "list"
357
551
358
552
// Enhanced filtering and sorting state
359
553
const [ sortBy , setSortBy ] = useState ( "addedDate" ) ; // Default sort by date added
@@ -623,6 +817,7 @@ const CippStandardDialog = ({
623
817
setShowOnlyNew ( false ) ;
624
818
setSortBy ( "addedDate" ) ;
625
819
setSortOrder ( "desc" ) ;
820
+ setViewMode ( "card" ) ; // Reset to card view
626
821
handleSearchQueryChange ( "" ) ;
627
822
} , [ handleSearchQueryChange ] ) ;
628
823
@@ -634,6 +829,7 @@ const CippStandardDialog = ({
634
829
setSelectedRecommendedBy ( [ ] ) ;
635
830
setSelectedTagFrameworks ( [ ] ) ;
636
831
setShowOnlyNew ( false ) ;
832
+ setViewMode ( "card" ) ; // Reset to card view
637
833
handleSearchQueryChange ( "" ) ; // Clear parent search state
638
834
handleCloseDialog ( ) ;
639
835
} , [ handleCloseDialog , handleSearchQueryChange ] ) ;
@@ -739,6 +935,32 @@ const CippStandardDialog = ({
739
935
sx = { { mb : 3 } }
740
936
/>
741
937
938
+ { /* View Toggle Controls */ }
939
+ < Box sx = { { display : 'flex' , justifyContent : 'space-between' , alignItems : 'center' , mb : 2 } } >
940
+ < Typography variant = "subtitle2" color = "text.secondary" >
941
+ View Mode:
942
+ </ Typography >
943
+ < ToggleButtonGroup
944
+ value = { viewMode }
945
+ exclusive
946
+ onChange = { ( e , newViewMode ) => {
947
+ if ( newViewMode !== null ) {
948
+ setViewMode ( newViewMode ) ;
949
+ }
950
+ } }
951
+ size = "small"
952
+ >
953
+ < ToggleButton value = "card" aria-label = "card view" >
954
+ < ViewModule sx = { { mr : 1 } } />
955
+ Cards
956
+ </ ToggleButton >
957
+ < ToggleButton value = "list" aria-label = "list view" >
958
+ < ViewList sx = { { mr : 1 } } />
959
+ List
960
+ </ ToggleButton >
961
+ </ ToggleButtonGroup >
962
+ </ Box >
963
+
742
964
{ /* Filter Controls Section */ }
743
965
< Box sx = { { mb : 2 } } >
744
966
{ /* Clickable header bar */ }
@@ -1050,7 +1272,19 @@ const CippStandardDialog = ({
1050
1272
< Typography variant = "body2" color = "textSecondary" sx = { { mb : 2 } } >
1051
1273
Showing { processedItems . length } standard{ processedItems . length !== 1 ? 's' : '' }
1052
1274
</ Typography >
1053
- < VirtualizedStandardGrid items = { processedItems } renderItem = { renderStandardCard } />
1275
+ { viewMode === "card" ? (
1276
+ < VirtualizedStandardGrid items = { processedItems } renderItem = { renderStandardCard } />
1277
+ ) : (
1278
+ < Box sx = { { maxHeight : "60vh" , overflow : "auto" } } >
1279
+ < CompactStandardList
1280
+ items = { processedItems }
1281
+ selectedStandards = { selectedStandards }
1282
+ handleToggleSingleStandard = { handleToggleSingleStandard }
1283
+ handleAddClick = { handleAddClick }
1284
+ isButtonDisabled = { isButtonDisabled }
1285
+ />
1286
+ </ Box >
1287
+ ) }
1054
1288
</ Box >
1055
1289
) }
1056
1290
</ DialogContent >
0 commit comments