Brijesh619 commented on code in PR #708:
URL: https://github.com/apache/atlas/pull/708#discussion_r3688424886


##########
dashboard/src/views/Administrator/Audits/AuditResults.tsx:
##########
@@ -153,89 +251,494 @@ const AuditResults = ({ componentProps, row }: any) => {
                             >
                               {name}
                             </Link>
-                          </ListItem>
-                        </>
-                      );
-                    })}
-                  </List>
-                </Item>
+                          }
+                        />
+                      </ListItem>
+                    );
+                  })}
+                </div>
+              );
+            })}
+        </List>
+      ) : operation === "IMPORT" || operation === "EXPORT" ? (
+        <ImportExportAudits auditObj={auditObj} />
+      ) : !isPurgeOperation ? (
+        <Typography>No Results Found</Typography>
+      ) : null}
+
+      {/* Purge Audit View */}
+      {isPurgeOperation ? (
+        <Box sx={{ mt: 2, mb: 2 }}>
+          <Box sx={{
+            p: 2.5,
+            borderRadius: 2,
+            backgroundColor: '#f0f4f8',
+            border: '1px solid rgba(0,0,0,0.08)',
+            boxShadow: '0 2px 8px rgba(0,0,0,0.02)'
+          }}>
+
+            {/* Run Id Header with Copy Action */}
+            {runId !== 'N/A' && (
+              <Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 
}}>
+                <Typography variant="body2" color="textSecondary" sx={{ 
fontFamily: 'monospace' }}>
+                  <strong>Run Id:</strong> {runId}
+                </Typography>
+                <Tooltip title={copiedRunId ? "Copied!" : "Copy Run Id"}>
+                  <IconButton
+                    size="small"
+                    onClick={() => {
+                      if (navigator.clipboard) {
+                        navigator.clipboard.writeText(runId);
+                      } else {
+                        const textField = document.createElement('textarea');
+                        textField.innerText = runId;
+                        document.body.appendChild(textField);
+                        textField.select();
+                        document.execCommand('copy');
+                        textField.remove();
+                      }
+                      setCopiedRunId(true);
+                      setTimeout(() => setCopiedRunId(false), 2000);
+                    }}
+                    sx={{ p: 0.5 }}
+                  >
+                    <ContentCopyIcon sx={{ fontSize: '15px', color: 
copiedRunId ? 'success.main' : 'text.secondary' }} />
+                  </IconButton>
+                </Tooltip>
+              </Box>
+            )}
+
+            {/* 4 Cards Grid: Requested, Total Purged, Failed (Display Only), 
Skipped (Display Only) */}
+            <Grid container spacing={2}>
+              {/* 1. Clickable Requested Card */}
+              {isSummaryRow && (
+                <Grid item xs={6} sm={3}>
+                  <Box
+                    onClick={() => {
+                      setActivePurgeView(PurgeActiveView.REQUESTED);
+                      setDrawerPage(1);
+                      setScrollTop(0);
+                    }}
+                    sx={{
+                      p: 1.5,
+                      borderRadius: 2,
+                      backgroundColor: '#eff6ff',
+                      border: '1px solid #bfdbfe',
+                      transition: 'all 0.2s ease-in-out',
+                      cursor: 'pointer',
+                      boxShadow: '0 1px 3px rgba(0,0,0,0.04)',
+                      '&:hover': {
+                        transform: 'translateY(-2px)',
+                        boxShadow: '0 4px 12px rgba(59,130,246,0.15)',
+                        borderColor: '#60a5fa'
+                      }
+                    }}
+                  >
+                    <Typography variant="caption" color="primary.main" 
display="block" sx={{ textTransform: 'uppercase', fontWeight: 'bold', 
letterSpacing: '0.5px', fontSize: '11px' }}>
+                      Requested
+                    </Typography>
+                    <Typography variant="h5" color="primary.main" sx={{ 
fontWeight: 'bold', mt: 0.5 }}>
+                      {requestedCount}
+                    </Typography>
+                  </Box>
+                </Grid>
+              )}
+
+              {/* 2. Clickable Total Purged Card */}
+              <Grid item xs={isSummaryRow ? 6 : 12} sm={isSummaryRow ? 3 : 4}>
+                <Box
+                  onClick={handleOpenPurgedDrawer}
+                  sx={{
+                    p: 1.5,
+                    borderRadius: 2,
+                    backgroundColor: '#f0fdf4',
+                    border: '1px solid #bbf7d0',
+                    transition: 'all 0.2s ease-in-out',
+                    cursor: totalPurgedCount > 0 ? 'pointer' : 'default',
+                    boxShadow: '0 1px 3px rgba(0,0,0,0.04)',
+                    '&:hover': totalPurgedCount > 0 ? {
+                      transform: 'translateY(-2px)',
+                      boxShadow: '0 4px 12px rgba(34,197,94,0.15)',
+                      borderColor: '#4ade80'
+                    } : {}
+                  }}
+                >
+                  <Typography variant="caption" color="success.main" 
display="block" sx={{ textTransform: 'uppercase', fontWeight: 'bold', 
letterSpacing: '0.5px', fontSize: '11px' }}>
+                    {isSummaryRow ? 'Total Purged' : 'Purged Entities'}
+                  </Typography>
+                  <Typography variant="h5" color="success.main" sx={{ 
fontWeight: 'bold', mt: 0.5 }}>
+                    {totalPurgedCount}
+                  </Typography>
+                </Box>
               </Grid>
-            </>
-          )}
-        </Grid>
-      ) : (
-        operation != "PURGE" &&
-        operation != "AUTO_PURGE" &&
-        operation != "IMPORT" &&
-        operation != "EXPORT" && <Typography>No Results Found</Typography>
-      )}
-
-      {(operation == "PURGE" || operation == "AUTO_PURGE") && 
!isEmpty(resultObj) ? (
-        <>
-          <Typography>{`${category[operation]}`}</Typography>
-          <List className="audit-results-list">
-            {resultObj.map((obj: string) => {
-              return (
-                <ListItem className="audit-results-list-item">
-                  <ListItemText
-                    primary={
-                      <Link
-                        className="audit-results-entityid"
-                        component="button"
-                        variant="body2"
-                        onClick={() => {
-                          setOpenPurgeModal(true);
-                          setCurrentPurgeResultObj(obj);
+
+              {/* 3 & 4. Display-Only Failed and Skipped Cards */}
+              {isSummaryRow && (
+                <>
+                  <Grid item xs={6} sm={3}>
+                    <LightTooltip
+                      title={
+                        failedCount > 0 || executionFailed
+                          ? "Some entities failed to purge. Please check 
<ATLAS_HOME>/logs/purgefailure.log for details."
+                          : "No failed entities during this purge operation."
+                      }
+                      arrow
+                      placement="top"
+                    >
+                      <Box
+                        sx={{
+                          p: 1.5,
+                          borderRadius: 2,
+                          backgroundColor: failedCount > 0 ? '#fef2f2' : 
'#fafafa',
+                          border: failedCount > 0 ? '1px solid #fecaca' : '1px 
solid rgba(0,0,0,0.08)',
+                          boxShadow: '0 1px 3px rgba(0,0,0,0.04)',
+                          cursor: 'default'
                         }}
-                        title={obj}
+                      >
+                        <Typography variant="caption" color={failedCount > 0 ? 
"error.main" : "textSecondary"} display="block" sx={{ textTransform: 
'uppercase', fontWeight: 'bold', letterSpacing: '0.5px', fontSize: '11px' }}>
+                          Failed
+                        </Typography>
+                        <Typography variant="h5" color={failedCount > 0 ? 
"error.main" : "textPrimary"} sx={{ fontWeight: 'bold', mt: 0.5 }}>
+                          {failedCount}
+                        </Typography>
+                      </Box>
+                    </LightTooltip>
+                  </Grid>
+
+                  {/* 4. Display-Only Skipped Card */}
+                  <Grid item xs={6} sm={3}>
+                    <LightTooltip
+                      title={
+                        skippedCount > 0 || executionFailed
+                          ? "Some entities were skipped during purge. Please 
check <ATLAS_HOME>/logs/purgefailure.log for details."
+                          : "No skipped entities during this purge operation."
+                      }
+                      arrow
+                      placement="top"
+                    >
+                      <Box
                         sx={{
-                          display: "inline-block",
-                          maxWidth: "100%",
-                          textOverflow: "ellipsis",
-                          overflow: "hidden",
-                          whiteSpace: "nowrap",
-                          textAlign: "left",
-                          verticalAlign: "bottom"
+                          p: 1.5,
+                          borderRadius: 2,
+                          backgroundColor: skippedCount > 0 ? '#fffbeb' : 
'#fafafa',
+                          border: skippedCount > 0 ? '1px solid #fef08a' : 
'1px solid rgba(0,0,0,0.08)',
+                          boxShadow: '0 1px 3px rgba(0,0,0,0.04)',
+                          cursor: 'default'
                         }}
                       >
-                        {obj}
-                      </Link>
-                    }
-                  />
-                </ListItem>
-              );
-            })}
-          </List>
-        </>
-      ) : (
-        (operation == "PURGE" || operation == "AUTO_PURGE") && <Typography>No 
Results Found</Typography>
-      )}
+                        <Typography variant="caption" color={skippedCount > 0 
? "warning.main" : "textSecondary"} display="block" sx={{ textTransform: 
'uppercase', fontWeight: 'bold', letterSpacing: '0.5px', fontSize: '11px' }}>
+                          Skipped
+                        </Typography>
+                        <Typography variant="h5" color={skippedCount > 0 ? 
"warning.main" : "textPrimary"} sx={{ fontWeight: 'bold', mt: 0.5 }}>
+                          {skippedCount}
+                        </Typography>
+                      </Box>
+                    </LightTooltip>
+                  </Grid>
+                </>
+              )}
+            </Grid>
 
-      {(operation == "IMPORT" || operation == "EXPORT") && (
-        <ImportExportAudits auditObj={auditObj} />
-      )}
+            {/* Status Alert if execution failure or failedCount > 0 */}
+            {executionFailed && (
+              <Box sx={{ mt: 2 }}>
+                <Alert severity="warning">
+                  <AlertTitle sx={{ fontWeight: 'bold', fontSize: '13px' 
}}>Partial success</AlertTitle>
+                  Some entities failed to purge. Check 
<strong>purgefailure.log</strong> for details.
+                </Alert>
+              </Box>
+            )}
+          </Box>
 
-      <TypeDefAuditDetailModal
-        open={openModal}
-        onClose={handleCloseModal}
-        detailObject={currentResultObj}
-        maxWidth="sm"
-      />
+          {/* Right Side Drawer — server-side pagination for Purged, 
client-side for Requested */}
+          {(() => {
+            // For REQUESTED view: client-side filter + paginate
+            // For PURGED view: server-side paginate, client-side search on 
current page
+            const isPurgedView = activePurgeView === PurgeActiveView.PURGED;
+            const isServerSidePagination = isPurgedView && isSummaryRow;
+
+            // Determine the raw list for the current view
+            const rawListForView: string[] = activePurgeView === 
PurgeActiveView.REQUESTED
+              ? requestedEntitiesList
+              : purgedApiGuids; // full list for non-summary, page list for 
summary
+
+            // Client-side search filter (on current page for purged, on full 
list for requested)
+            const filteredList = rawListForView.filter((guidStr: string) => {
+              if (!drawerSearchText) return true;
+              return 
guidStr.toLowerCase().includes(drawerSearchText.trim().toLowerCase());
+            });
+
+            // For client-side pagination (requested OR non-summary purged)
+            const clientSideItems = !isServerSidePagination
+              ? filteredList.slice(0, drawerPage * drawerPageSize)
+              : [];
+
+            const displayItems = isServerSidePagination ? filteredList : 
clientSideItems;
+
+            const { visibleItems, paddingTop, paddingBottom, startIndex } = 
useVirtualization({

Review Comment:
   Extracted the drawer logic into a new PurgeEntitiesDrawer component so 
useVirtualization is called safely at the top level, fixing the Hooks violation.



##########
dashboard/src/views/Administrator/Audits/AuditResults.tsx:
##########
@@ -153,89 +251,494 @@ const AuditResults = ({ componentProps, row }: any) => {
                             >
                               {name}
                             </Link>
-                          </ListItem>
-                        </>
-                      );
-                    })}
-                  </List>
-                </Item>
+                          }
+                        />
+                      </ListItem>
+                    );
+                  })}
+                </div>
+              );
+            })}
+        </List>
+      ) : operation === "IMPORT" || operation === "EXPORT" ? (
+        <ImportExportAudits auditObj={auditObj} />
+      ) : !isPurgeOperation ? (
+        <Typography>No Results Found</Typography>
+      ) : null}
+
+      {/* Purge Audit View */}
+      {isPurgeOperation ? (
+        <Box sx={{ mt: 2, mb: 2 }}>
+          <Box sx={{
+            p: 2.5,
+            borderRadius: 2,
+            backgroundColor: '#f0f4f8',
+            border: '1px solid rgba(0,0,0,0.08)',
+            boxShadow: '0 2px 8px rgba(0,0,0,0.02)'
+          }}>
+
+            {/* Run Id Header with Copy Action */}
+            {runId !== 'N/A' && (
+              <Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 
}}>
+                <Typography variant="body2" color="textSecondary" sx={{ 
fontFamily: 'monospace' }}>
+                  <strong>Run Id:</strong> {runId}
+                </Typography>
+                <Tooltip title={copiedRunId ? "Copied!" : "Copy Run Id"}>
+                  <IconButton
+                    size="small"
+                    onClick={() => {
+                      if (navigator.clipboard) {
+                        navigator.clipboard.writeText(runId);
+                      } else {
+                        const textField = document.createElement('textarea');
+                        textField.innerText = runId;
+                        document.body.appendChild(textField);
+                        textField.select();
+                        document.execCommand('copy');
+                        textField.remove();
+                      }
+                      setCopiedRunId(true);
+                      setTimeout(() => setCopiedRunId(false), 2000);
+                    }}
+                    sx={{ p: 0.5 }}
+                  >
+                    <ContentCopyIcon sx={{ fontSize: '15px', color: 
copiedRunId ? 'success.main' : 'text.secondary' }} />
+                  </IconButton>
+                </Tooltip>
+              </Box>
+            )}
+
+            {/* 4 Cards Grid: Requested, Total Purged, Failed (Display Only), 
Skipped (Display Only) */}
+            <Grid container spacing={2}>
+              {/* 1. Clickable Requested Card */}
+              {isSummaryRow && (
+                <Grid item xs={6} sm={3}>
+                  <Box
+                    onClick={() => {
+                      setActivePurgeView(PurgeActiveView.REQUESTED);
+                      setDrawerPage(1);
+                      setScrollTop(0);
+                    }}
+                    sx={{
+                      p: 1.5,
+                      borderRadius: 2,
+                      backgroundColor: '#eff6ff',
+                      border: '1px solid #bfdbfe',
+                      transition: 'all 0.2s ease-in-out',
+                      cursor: 'pointer',
+                      boxShadow: '0 1px 3px rgba(0,0,0,0.04)',
+                      '&:hover': {
+                        transform: 'translateY(-2px)',
+                        boxShadow: '0 4px 12px rgba(59,130,246,0.15)',
+                        borderColor: '#60a5fa'
+                      }
+                    }}
+                  >
+                    <Typography variant="caption" color="primary.main" 
display="block" sx={{ textTransform: 'uppercase', fontWeight: 'bold', 
letterSpacing: '0.5px', fontSize: '11px' }}>
+                      Requested
+                    </Typography>
+                    <Typography variant="h5" color="primary.main" sx={{ 
fontWeight: 'bold', mt: 0.5 }}>
+                      {requestedCount}
+                    </Typography>
+                  </Box>
+                </Grid>
+              )}
+
+              {/* 2. Clickable Total Purged Card */}
+              <Grid item xs={isSummaryRow ? 6 : 12} sm={isSummaryRow ? 3 : 4}>
+                <Box
+                  onClick={handleOpenPurgedDrawer}
+                  sx={{
+                    p: 1.5,
+                    borderRadius: 2,
+                    backgroundColor: '#f0fdf4',
+                    border: '1px solid #bbf7d0',
+                    transition: 'all 0.2s ease-in-out',
+                    cursor: totalPurgedCount > 0 ? 'pointer' : 'default',
+                    boxShadow: '0 1px 3px rgba(0,0,0,0.04)',
+                    '&:hover': totalPurgedCount > 0 ? {
+                      transform: 'translateY(-2px)',
+                      boxShadow: '0 4px 12px rgba(34,197,94,0.15)',
+                      borderColor: '#4ade80'
+                    } : {}
+                  }}
+                >
+                  <Typography variant="caption" color="success.main" 
display="block" sx={{ textTransform: 'uppercase', fontWeight: 'bold', 
letterSpacing: '0.5px', fontSize: '11px' }}>
+                    {isSummaryRow ? 'Total Purged' : 'Purged Entities'}
+                  </Typography>
+                  <Typography variant="h5" color="success.main" sx={{ 
fontWeight: 'bold', mt: 0.5 }}>
+                    {totalPurgedCount}
+                  </Typography>
+                </Box>
               </Grid>
-            </>
-          )}
-        </Grid>
-      ) : (
-        operation != "PURGE" &&
-        operation != "AUTO_PURGE" &&
-        operation != "IMPORT" &&
-        operation != "EXPORT" && <Typography>No Results Found</Typography>
-      )}
-
-      {(operation == "PURGE" || operation == "AUTO_PURGE") && 
!isEmpty(resultObj) ? (
-        <>
-          <Typography>{`${category[operation]}`}</Typography>
-          <List className="audit-results-list">
-            {resultObj.map((obj: string) => {
-              return (
-                <ListItem className="audit-results-list-item">
-                  <ListItemText
-                    primary={
-                      <Link
-                        className="audit-results-entityid"
-                        component="button"
-                        variant="body2"
-                        onClick={() => {
-                          setOpenPurgeModal(true);
-                          setCurrentPurgeResultObj(obj);
+
+              {/* 3 & 4. Display-Only Failed and Skipped Cards */}
+              {isSummaryRow && (
+                <>
+                  <Grid item xs={6} sm={3}>
+                    <LightTooltip
+                      title={
+                        failedCount > 0 || executionFailed
+                          ? "Some entities failed to purge. Please check 
<ATLAS_HOME>/logs/purgefailure.log for details."
+                          : "No failed entities during this purge operation."
+                      }
+                      arrow
+                      placement="top"
+                    >
+                      <Box
+                        sx={{
+                          p: 1.5,
+                          borderRadius: 2,
+                          backgroundColor: failedCount > 0 ? '#fef2f2' : 
'#fafafa',
+                          border: failedCount > 0 ? '1px solid #fecaca' : '1px 
solid rgba(0,0,0,0.08)',
+                          boxShadow: '0 1px 3px rgba(0,0,0,0.04)',
+                          cursor: 'default'
                         }}
-                        title={obj}
+                      >
+                        <Typography variant="caption" color={failedCount > 0 ? 
"error.main" : "textSecondary"} display="block" sx={{ textTransform: 
'uppercase', fontWeight: 'bold', letterSpacing: '0.5px', fontSize: '11px' }}>
+                          Failed
+                        </Typography>
+                        <Typography variant="h5" color={failedCount > 0 ? 
"error.main" : "textPrimary"} sx={{ fontWeight: 'bold', mt: 0.5 }}>
+                          {failedCount}
+                        </Typography>
+                      </Box>
+                    </LightTooltip>
+                  </Grid>
+
+                  {/* 4. Display-Only Skipped Card */}
+                  <Grid item xs={6} sm={3}>
+                    <LightTooltip
+                      title={
+                        skippedCount > 0 || executionFailed
+                          ? "Some entities were skipped during purge. Please 
check <ATLAS_HOME>/logs/purgefailure.log for details."
+                          : "No skipped entities during this purge operation."
+                      }
+                      arrow
+                      placement="top"
+                    >
+                      <Box
                         sx={{
-                          display: "inline-block",
-                          maxWidth: "100%",
-                          textOverflow: "ellipsis",
-                          overflow: "hidden",
-                          whiteSpace: "nowrap",
-                          textAlign: "left",
-                          verticalAlign: "bottom"
+                          p: 1.5,
+                          borderRadius: 2,
+                          backgroundColor: skippedCount > 0 ? '#fffbeb' : 
'#fafafa',
+                          border: skippedCount > 0 ? '1px solid #fef08a' : 
'1px solid rgba(0,0,0,0.08)',
+                          boxShadow: '0 1px 3px rgba(0,0,0,0.04)',
+                          cursor: 'default'
                         }}
                       >
-                        {obj}
-                      </Link>
-                    }
-                  />
-                </ListItem>
-              );
-            })}
-          </List>
-        </>
-      ) : (
-        (operation == "PURGE" || operation == "AUTO_PURGE") && <Typography>No 
Results Found</Typography>
-      )}
+                        <Typography variant="caption" color={skippedCount > 0 
? "warning.main" : "textSecondary"} display="block" sx={{ textTransform: 
'uppercase', fontWeight: 'bold', letterSpacing: '0.5px', fontSize: '11px' }}>
+                          Skipped
+                        </Typography>
+                        <Typography variant="h5" color={skippedCount > 0 ? 
"warning.main" : "textPrimary"} sx={{ fontWeight: 'bold', mt: 0.5 }}>
+                          {skippedCount}
+                        </Typography>
+                      </Box>
+                    </LightTooltip>
+                  </Grid>
+                </>
+              )}
+            </Grid>
 
-      {(operation == "IMPORT" || operation == "EXPORT") && (
-        <ImportExportAudits auditObj={auditObj} />
-      )}
+            {/* Status Alert if execution failure or failedCount > 0 */}
+            {executionFailed && (
+              <Box sx={{ mt: 2 }}>
+                <Alert severity="warning">
+                  <AlertTitle sx={{ fontWeight: 'bold', fontSize: '13px' 
}}>Partial success</AlertTitle>
+                  Some entities failed to purge. Check 
<strong>purgefailure.log</strong> for details.
+                </Alert>
+              </Box>
+            )}
+          </Box>
 
-      <TypeDefAuditDetailModal
-        open={openModal}
-        onClose={handleCloseModal}
-        detailObject={currentResultObj}
-        maxWidth="sm"
-      />
+          {/* Right Side Drawer — server-side pagination for Purged, 
client-side for Requested */}
+          {(() => {
+            // For REQUESTED view: client-side filter + paginate
+            // For PURGED view: server-side paginate, client-side search on 
current page
+            const isPurgedView = activePurgeView === PurgeActiveView.PURGED;
+            const isServerSidePagination = isPurgedView && isSummaryRow;
+
+            // Determine the raw list for the current view
+            const rawListForView: string[] = activePurgeView === 
PurgeActiveView.REQUESTED
+              ? requestedEntitiesList
+              : purgedApiGuids; // full list for non-summary, page list for 
summary
+
+            // Client-side search filter (on current page for purged, on full 
list for requested)
+            const filteredList = rawListForView.filter((guidStr: string) => {
+              if (!drawerSearchText) return true;
+              return 
guidStr.toLowerCase().includes(drawerSearchText.trim().toLowerCase());
+            });
+
+            // For client-side pagination (requested OR non-summary purged)
+            const clientSideItems = !isServerSidePagination
+              ? filteredList.slice(0, drawerPage * drawerPageSize)
+              : [];
+
+            const displayItems = isServerSidePagination ? filteredList : 
clientSideItems;
+
+            const { visibleItems, paddingTop, paddingBottom, startIndex } = 
useVirtualization({
+                items: displayItems,
+                scrollTop,
+                itemHeight: 37
+            });
 
-      {(operation == "PURGE" || operation == "AUTO_PURGE") && (
-        <CustomModal
-          open={openPurgeModal}
-          onClose={handleClosePurgeModal}
-          title={operation == "PURGE" ? `Purged Entity Details: 
${currentPurgeResultObj}` : `Auto Purged Entity Details: 
${currentPurgeResultObj}`}
-          footer={false}
-          button1Handler={undefined}
-          button2Handler={undefined}
-          maxWidth="lg"
-        >
-          <AuditsTab auditResultGuid={currentPurgeResultObj} loading={false} />
-        </CustomModal>
-      )}
+            // ---- Purged: server-side pages ----
+            // Server side infinite scroll handles data loading, we just 
display what we have
+            const displayTotal = isServerSidePagination ? purgedTotalCount : 
filteredList.length;
+
+            const handleDrawerScroll = (e: React.UIEvent<HTMLUListElement>) => 
{
+              setScrollTop(e.currentTarget.scrollTop);
+              if (drawerScrollTimerRef.current) return;
+
+              // If it's a wheel event and they are scrolling up, ignore it
+              const { scrollTop, scrollHeight, clientHeight } = 
e.currentTarget;
+              const isNearBottom = scrollHeight - scrollTop - clientHeight < 
20;
+              if (isNearBottom) {
+                drawerScrollTimerRef.current = setTimeout(() => {
+                  drawerScrollTimerRef.current = null;
+                  if (isServerSidePagination && !loadingPurgedApi && 
purgedApiGuids.length < displayTotal) {
+                    fetchPurged(true);
+                  } else if (!isServerSidePagination && visibleItems.length < 
displayTotal) {
+                    setDrawerPage(prev => prev + 1);
+                  }
+                }, 250);
+              }
+            };
+
+            return (
+              <Drawer
+                anchor="right"
+                open={activePurgeView !== PurgeActiveView.NONE}
+                onClose={() => {
+                  setActivePurgeView(PurgeActiveView.NONE);
+                  setDrawerSearchText('');
+                  setDrawerPage(1);
+                  setScrollTop(0);
+                }}
+                PaperProps={{ sx: { width: '460px', p: 2.5, display: 'flex', 
flexDirection: 'column', height: '100%', overflow: 'hidden' } }}
+              >
+                {/* 1. Drawer Header */}
+                <Stack direction="row" justifyContent="space-between" 
alignItems="center" sx={{ mb: 2 }}>
+                  <Typography sx={{ fontWeight: 600, color: 'text.primary', 
fontSize: '20px' }}>
+                    {activePurgeView === PurgeActiveView.REQUESTED ? 
'Requested Entities' : 'Purged Entities'}
+                  </Typography>
+                  <IconButton
+                    onClick={() => {
+                      setActivePurgeView(PurgeActiveView.NONE);
+                      setDrawerSearchText('');
+                      setDrawerPage(1);
+                      setScrollTop(0);
+                    }}
+                    size="small"
+                  >
+                    ✕
+                  </IconButton>
+                </Stack>
+
+                {/* 2. Run Id Header inside Drawer */}
+                {runId !== 'N/A' && (
+                  <Box sx={{ display: 'flex', alignItems: 'center', gap: 1, 
mb: 2, py: 1, px: 1.5, bgcolor: '#f8fafc', borderRadius: 1.5, border: '1px 
solid #e2e8f0' }}>
+                    <Typography variant="caption" color="textSecondary" sx={{ 
fontSize: '12px' }}>
+                      <strong>Run Id:</strong> {runId}
+                    </Typography>
+                    <Tooltip title={copiedRunId ? "Copied!" : "Copy Run Id"}>
+                      <IconButton
+                        size="small"
+                        onClick={() => {
+                          if (navigator.clipboard) {
+                            navigator.clipboard.writeText(runId);
+                          } else {
+                            const textField = 
document.createElement('textarea');
+                            textField.innerText = runId;
+                            document.body.appendChild(textField);
+                            textField.select();
+                            document.execCommand('copy');
+                            textField.remove();
+                          }
+                          setCopiedRunId(true);
+                          setTimeout(() => setCopiedRunId(false), 2000);
+                        }}
+                        sx={{ p: 0.5, ml: 'auto' }}
+                      >
+                        <ContentCopyIcon sx={{ fontSize: '15px', color: 
copiedRunId ? 'success.main' : 'text.secondary' }} />
+                      </IconButton>
+                    </Tooltip>
+                  </Box>
+                )}
+
+                {/* 3. Search Bar */}
+                {(activePurgeView === 'requested' ? 
requestedEntitiesList.length > 0 : purgedApiGuids.length > 0 || 
loadingPurgedApi) && (
+                  <Box sx={{ mb: 2 }}>
+                    <TextField
+                      fullWidth
+                      size="small"
+                      placeholder="Search GUIDs..."
+                      value={drawerSearchText}
+                      onChange={(e) => {
+                        setDrawerSearchText(e.target.value);
+                        setDrawerPage(1);
+                        setScrollTop(0);
+                        // For purged view: re-fetch page 1 with new search is 
not supported server-side;
+                        // search is applied client-side on the current page's 
data
+                      }}
+                      InputProps={{
+                        startAdornment: (
+                          <InputAdornment position="start">
+                            <SearchIcon sx={{ fontSize: 18, color: 
'text.secondary' }} />
+                          </InputAdornment>
+                        ),
+                        endAdornment: drawerSearchText ? (
+                          <InputAdornment position="end">
+                            <IconButton size="small" onClick={() => {
+                              setDrawerSearchText('');
+                              setDrawerPage(1);
+                              setScrollTop(0);
+                            }}>
+                              ✕
+                            </IconButton>
+                          </InputAdornment>
+                        ) : null
+                      }}
+                      sx={{
+                        '& .MuiOutlinedInput-root': {
+                          borderRadius: 2,
+                          bgcolor: '#fff'
+                        }
+                      }}
+                    />
+                  </Box>
+                )}
+
+                <Divider sx={{ mb: 2 }} />
+
+                {/* 4. GUID List */}
+                {isPurgedView && loadingPurgedApi && purgedApiGuids.length === 
0 ? (
+                  <Box sx={{ display: 'flex', justifyContent: 'center', py: 5 
}}>
+                    <CircularProgress size={30} />
+                  </Box>
+                ) : (
+                  <List dense sx={{ overflowY: 'scroll', flexGrow: 1, 
minHeight: 0 }} onScroll={handleDrawerScroll} onWheel={handleDrawerScroll}>
+                    {(() => {
+                      if (displayItems.length === 0) {
+                        return (
+                          <Typography variant="body2" color="textSecondary" 
sx={{ py: 3, textAlign: 'center' }}>
+                            No matching GUIDs found
+                          </Typography>
+                        );
+                      }
+
+                      return (
+                        <>
+                          {paddingTop > 0 && <div style={{ height: paddingTop 
}} />}
+                          {visibleItems.map((guidStr: string, localIndex: 
number) => {
+                            const index = startIndex + localIndex;
+                            const globalIndex = index + 1;
+                            return (
+                              <ListItem key={guidStr + index} sx={{ 
borderBottom: '1px solid rgba(0,0,0,0.04)', py: 1, height: '37px', boxSizing: 
'border-box' }}>
+                                <Typography variant="body2" sx={{ mr: 1, 
minWidth: '24px', color: 'text.secondary' }}>{globalIndex}.</Typography>
+                                <Link
+                                  component="button"
+                                  variant="body2"
+                                  underline="hover"
+                                  onClick={() => {
+                                    setOpenPurgeModal(true);
+                                    setCurrentPurgeResultObj(guidStr);
+                                  }}
+                                  title={guidStr}
+                                  sx={{
+                                    display: "inline-block",
+                                    maxWidth: "100%",
+                                    textOverflow: "ellipsis",
+                                    overflow: "hidden",
+                                    whiteSpace: "nowrap",
+                                    textAlign: "left"
+                                  }}
+                                >
+                                  {guidStr}
+                                </Link>
+                              </ListItem>
+                            );
+                          })}
+                          {paddingBottom > 0 && <div style={{ height: 
paddingBottom }} />}
+                        </>
+                      );
+                    })()}
+
+                    {displayItems.length > 0 && displayItems.length < 
displayTotal && (
+                      <Box sx={{ display: 'flex', justifyContent: 'center', 
alignItems: 'center', py: 2, gap: 1 }}>
+                        {isPurgedView && loadingPurgedApi && <CircularProgress 
size={16} />}
+                        <Typography
+                          variant="caption"
+                          color="text.secondary"
+                          sx={{ fontStyle: 'italic', cursor: 'default' }}
+                        >
+                          {isPurgedView && loadingPurgedApi ? 'Loading 
more...' : 'Scroll to load more data'}
+                        </Typography>
+                      </Box>
+                    )}
+                  </List>
+                )}
+
+                {/* 5. Relationship-card-style Footer: Showing X of Y | Limit 
[input] */}
+                {displayTotal > 0 && (
+                  <Box sx={{
+                    mt: 'auto',
+                    pt: 1.5,
+                    borderTop: '1px solid #e2e8f0',
+                    display: 'flex',
+                    alignItems: 'center',
+                    justifyContent: 'space-between',
+                    flexShrink: 0
+                  }}>
+                    <Typography variant="caption" color="textSecondary">
+                      Showing {displayItems.length} of {displayTotal}
+                    </Typography>
+                    <Box sx={{ display: 'flex', alignItems: 'center', gap: 
0.75 }}>
+                      <Typography variant="caption" 
color="textSecondary">Limit</Typography>
+                      <Box
+                        component="input"
+                        type="number"
+                        min={1}
+                        value={drawerPageSizeInput}
+                        onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
+                          setDrawerPageSizeInput(e.target.value);
+                        }}
+                        onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) 
=> {
+                          if (e.key === 'Enter') {
+                            const parsed = parseInt((e.target as 
HTMLInputElement).value, 10);
+                            if (Number.isFinite(parsed) && parsed > 0) {
+                              const clamped = Math.min(parsed, displayTotal);
+                              setDrawerPageSize(clamped);
+                              setDrawerPageSizeInput(String(clamped));
+                              setDrawerPage(1);
+                              if (isServerSidePagination) {
+                                fetchPurged(false, clamped);
+                              }
+                            }
+                          }
+                        }}
+                        sx={{
+                          width: '56px',
+                          fontSize: '12px',
+                          border: '1px solid #cbd5e1',
+                          borderRadius: '4px',
+                          px: 0.75,
+                          py: 0.25,
+                          textAlign: 'center',
+                          outline: 'none',
+                          '&:focus': { borderColor: '#90caf9' }
+                        }}
+                      />
+                    </Box>
+                  </Box>
+                )}
+              </Drawer>
+            );
+          })()}
+        </Box>
+      ) : null}
     </>
   );
 };
+
 export default AuditResults;
+// trigger hmr

Review Comment:
   Removed the leftover debugging comment.



##########
dashboardv2/public/css/scss/style.scss:
##########
@@ -39,4 +39,4 @@
 @import "override.scss";
 @import "trumbowyg.scss";
 @import "texteditor.scss";
-@import "downloads.scss";
\ No newline at end of file
+@import "downloads.scss";@import "drawer.scss";

Review Comment:
   Separated the @import statements onto multiple lines for better readability



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to