Skip to content

Commit 60cc854

Browse files
Merge pull request #4349 from Jr7468/StandardListView
View options for Standard templates
2 parents c8019cb + 9dc920e commit 60cc854

File tree

1 file changed

+236
-2
lines changed

1 file changed

+236
-2
lines changed

src/components/CippStandards/CippStandardDialog.jsx

Lines changed: 236 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,15 @@ import {
2222
Stack,
2323
Divider,
2424
Collapse,
25+
ToggleButton,
26+
ToggleButtonGroup,
27+
List,
28+
ListItem,
29+
ListItemText,
30+
ListItemSecondaryAction,
2531
} from "@mui/material";
2632
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";
2834
import { useState, useCallback, useMemo, memo, useEffect } from "react";
2935
import { debounce } from "lodash";
3036
import { Virtuoso } from "react-virtuoso";
@@ -341,6 +347,193 @@ const VirtualizedStandardGrid = memo(({ items, renderItem }) => {
341347

342348
VirtualizedStandardGrid.displayName = "VirtualizedStandardGrid";
343349

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+
344537
const CippStandardDialog = ({
345538
dialogOpen,
346539
handleCloseDialog,
@@ -354,6 +547,7 @@ const CippStandardDialog = ({
354547
const [isButtonDisabled, setButtonDisabled] = useState(false);
355548
const [localSearchQuery, setLocalSearchQuery] = useState("");
356549
const [isInitialLoading, setIsInitialLoading] = useState(true);
550+
const [viewMode, setViewMode] = useState("card"); // "card" or "list"
357551

358552
// Enhanced filtering and sorting state
359553
const [sortBy, setSortBy] = useState("addedDate"); // Default sort by date added
@@ -623,6 +817,7 @@ const CippStandardDialog = ({
623817
setShowOnlyNew(false);
624818
setSortBy("addedDate");
625819
setSortOrder("desc");
820+
setViewMode("card"); // Reset to card view
626821
handleSearchQueryChange("");
627822
}, [handleSearchQueryChange]);
628823

@@ -634,6 +829,7 @@ const CippStandardDialog = ({
634829
setSelectedRecommendedBy([]);
635830
setSelectedTagFrameworks([]);
636831
setShowOnlyNew(false);
832+
setViewMode("card"); // Reset to card view
637833
handleSearchQueryChange(""); // Clear parent search state
638834
handleCloseDialog();
639835
}, [handleCloseDialog, handleSearchQueryChange]);
@@ -739,6 +935,32 @@ const CippStandardDialog = ({
739935
sx={{ mb: 3 }}
740936
/>
741937

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+
742964
{/* Filter Controls Section */}
743965
<Box sx={{ mb: 2 }}>
744966
{/* Clickable header bar */}
@@ -1050,7 +1272,19 @@ const CippStandardDialog = ({
10501272
<Typography variant="body2" color="textSecondary" sx={{ mb: 2 }}>
10511273
Showing {processedItems.length} standard{processedItems.length !== 1 ? 's' : ''}
10521274
</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+
)}
10541288
</Box>
10551289
)}
10561290
</DialogContent>

0 commit comments

Comments
 (0)