Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ import {
downloadProjectListPdf,
fetchAllProjectsForExport,
} from "@features/project-hub/utils/projectsExport";
import {
downloadCaseListCsv,
downloadCaseListPdf,
fetchAllCasesForExport,
} from "@features/project-hub/utils/casesExport";
import { getSeverityLegendColor } from "@features/dashboard/utils/dashboard";
import { formatCasesTableCaseIdentifier, getStatusColor } from "@features/dashboard/utils/casesTable";
import { mapSeverityToDisplay } from "@features/support/utils/support";
Expand Down Expand Up @@ -134,7 +139,7 @@ export default function PartnerGlobalSearch(): JSX.Element {
isExportingRef.current = true;
setExportingFormat(format);
try {
const allProjects = await fetchAllProjectsForExport(authFetch);
const allProjects = await fetchAllProjectsForExport(authFetch, debouncedSearchQuery);
if (allProjects.length === 0) {
showError("No projects to export.");
return;
Expand All @@ -151,11 +156,49 @@ export default function PartnerGlobalSearch(): JSX.Element {
setExportingFormat(null);
}
},
[authFetch, showError],
[authFetch, debouncedSearchQuery, showError],
);

const isExporting = exportingFormat !== null;

const [exportingCasesFormat, setExportingCasesFormat] = useState<ExportFormat | null>(null);
const isExportingCasesRef = useRef(false);
const [exportCasesAnchorEl, setExportCasesAnchorEl] = useState<HTMLElement | null>(null);

const handleCasesExportOpen = (e: MouseEvent<HTMLElement>) => {
if (!isExportingCasesRef.current) setExportCasesAnchorEl(e.currentTarget);
};
const handleCasesExportClose = () => setExportCasesAnchorEl(null);

const handleCasesExport = useCallback(
async (format: ExportFormat) => {
if (isExportingCasesRef.current) return;
handleCasesExportClose();
isExportingCasesRef.current = true;
setExportingCasesFormat(format);
try {
const allCases = await fetchAllCasesForExport(authFetch, debouncedSearchQuery);
if (allCases.length === 0) {
showError("No cases to export.");
return;
}
if (format === "csv") {
downloadCaseListCsv(allCases);
} else {
downloadCaseListPdf(allCases);
}
} catch {
showError("Failed to export cases.");
} finally {
isExportingCasesRef.current = false;
setExportingCasesFormat(null);
}
},
[authFetch, debouncedSearchQuery, showError],
);

const isExportingCases = exportingCasesFormat !== null;

// Table query — filters by the debounced search query when one is active
const {
data: tableData,
Expand Down Expand Up @@ -438,14 +481,12 @@ export default function PartnerGlobalSearch(): JSX.Element {
</Box>
<Box sx={{ flex: 1, minWidth: 0 }}>
<Typography noWrap variant="body2">
{highlightMatch(
c.number
? `${c.number} · ${c.title ?? ""}`
: (c.title ?? ""),
debouncedSearchQuery,
)}
{highlightMatch(c.title ?? "--", debouncedSearchQuery)}
</Typography>
<Typography color="text.secondary" variant="caption">
<Typography color="text.secondary" noWrap variant="caption">
{formatCasesTableCaseIdentifier(c.number, c.internalId)}
</Typography>
<Typography color="text.secondary" noWrap variant="caption">
{caseTypeLabel}
</Typography>
</Box>
Expand Down Expand Up @@ -638,21 +679,52 @@ export default function PartnerGlobalSearch(): JSX.Element {

{/* Cases section */}
<Box>
<Box sx={{ alignItems: "center", display: "flex", gap: 1, mb: 1.5 }}>
<FileText size={20} />
<Typography variant="h6">
Cases
{!isLoadingCases && (
<Typography
color="text.secondary"
component="span"
variant="h6"
>
{" "}
({casesTotal})
</Typography>
)}
</Typography>
<Box sx={{ alignItems: "center", display: "flex", mb: 1.5 }}>
<Box sx={{ alignItems: "center", display: "flex", flex: 1, gap: 1 }}>
<FileText size={20} />
<Typography variant="h6">
Cases
{!isLoadingCases && (
<Typography
color="text.secondary"
component="span"
variant="h6"
>
{" "}
({casesTotal})
</Typography>
)}
</Typography>
</Box>
<Button
aria-controls="partner-cases-export-menu"
aria-expanded={Boolean(exportCasesAnchorEl)}
aria-haspopup="menu"
disabled={isExportingCases || (!isLoadingCases && casesTotal === 0)}
endIcon={<ChevronDown size={16} />}
onClick={handleCasesExportOpen}
size="small"
startIcon={
isExportingCases ? (
<CircularProgress color="inherit" size={16} />
) : (
<Download size={16} />
)
}
type="button"
variant="outlined"
>
{isExportingCases ? "Exporting..." : "Export"}
</Button>
<Menu
anchorEl={exportCasesAnchorEl}
id="partner-cases-export-menu"
onClose={handleCasesExportClose}
open={Boolean(exportCasesAnchorEl)}
>
<MenuItem onClick={() => void handleCasesExport("csv")}>Export to CSV</MenuItem>
<MenuItem onClick={() => void handleCasesExport("pdf")}>Export to PDF</MenuItem>
</Menu>
</Box>

<TableContainer component={Paper} sx={{ overflowX: "auto" }}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@
import {
alpha,
Box,
Button,
Chip,
CircularProgress,
IconButton,
InputAdornment,
LinearProgress,
Menu,
MenuItem,
Paper,
Skeleton,
Table,
Expand All @@ -33,8 +37,8 @@ import {
TextField,
Typography,
} from "@wso2/oxygen-ui";
import { ArrowLeft, FileText, Search, X } from "@wso2/oxygen-ui-icons-react";
import { type ChangeEvent, type JSX, useEffect, useState } from "react";
import { ArrowLeft, ChevronDown, Download, FileText, Search, X } from "@wso2/oxygen-ui-icons-react";
import { type ChangeEvent, type JSX, type MouseEvent, useCallback, useEffect, useRef, useState } from "react";
import { useNavigate, useSearchParams } from "react-router";
import { useGetGlobalSearch } from "@api/useGetGlobalSearch";
import { useDebouncedValue } from "@hooks/useDebouncedValue";
Expand All @@ -43,6 +47,13 @@ import { getSeverityLegendColor } from "@features/dashboard/utils/dashboard";
import { formatCasesTableCaseIdentifier, getStatusColor } from "@features/dashboard/utils/casesTable";
import { mapSeverityToDisplay } from "@features/support/utils/support";
import { getCaseNavigationPath, getCaseTypeChipProps } from "@features/project-hub/utils/globalSearchNavigation";
import {
downloadCaseListCsv,
downloadCaseListPdf,
fetchAllCasesForExport,
} from "@features/project-hub/utils/casesExport";
import { useAuthApiClient } from "@/hooks/useAuthApiClient";
import { useErrorBanner } from "@context/error-banner/ErrorBannerContext";

const ROWS_PER_PAGE_OPTIONS = [10, 25, 50];
const DEFAULT_ROWS_PER_PAGE = 10;
Expand All @@ -55,8 +66,12 @@ const COL_SPAN = 6;
* Navigated to via "View More" on the partner global search page.
* Pre-fills the search bar from the `?q=` URL parameter.
*/
type ExportFormat = "csv" | "pdf";

export default function PartnerCasesPage(): JSX.Element {
const navigate = useNavigate();
const authFetch = useAuthApiClient();
const { showError } = useErrorBanner();
const [searchParams, setSearchParams] = useSearchParams();
const urlQuery = searchParams.get("q") ?? "";

Expand Down Expand Up @@ -94,6 +109,44 @@ export default function PartnerCasesPage(): JSX.Element {
const cases = data?.cases ?? [];
const totalRecords = data?.casesTotal ?? 0;

const [exportingFormat, setExportingFormat] = useState<ExportFormat | null>(null);
const isExportingRef = useRef(false);
const [exportAnchorEl, setExportAnchorEl] = useState<HTMLElement | null>(null);

const handleExportOpen = (e: MouseEvent<HTMLElement>) => {
if (!isExportingRef.current) setExportAnchorEl(e.currentTarget);
};
const handleExportClose = () => setExportAnchorEl(null);

const handleExport = useCallback(
async (format: ExportFormat) => {
if (isExportingRef.current) return;
handleExportClose();
isExportingRef.current = true;
setExportingFormat(format);
try {
const allCases = await fetchAllCasesForExport(authFetch, debouncedSearchQuery);
if (allCases.length === 0) {
showError("No cases to export.");
return;
}
if (format === "csv") {
downloadCaseListCsv(allCases);
} else {
downloadCaseListPdf(allCases);
}
} catch {
showError("Failed to export cases.");
} finally {
isExportingRef.current = false;
setExportingFormat(null);
}
},
[authFetch, debouncedSearchQuery, showError],
);

const isExporting = exportingFormat !== null;

const handleChangePage = (_: unknown, newPage: number) => setPage(newPage);
const handleChangeRowsPerPage = (e: ChangeEvent<HTMLInputElement>) => {
setRowsPerPage(parseInt(e.target.value, 10));
Expand Down Expand Up @@ -146,6 +199,35 @@ export default function PartnerCasesPage(): JSX.Element {
/>
)}
</Typography>
<Button
aria-controls="cases-page-export-menu"
aria-expanded={Boolean(exportAnchorEl)}
aria-haspopup="menu"
disabled={isExporting || (!isLoading && totalRecords === 0)}
endIcon={<ChevronDown size={16} />}
onClick={handleExportOpen}
size="small"
startIcon={
isExporting ? (
<CircularProgress color="inherit" size={16} />
) : (
<Download size={16} />
)
}
type="button"
variant="outlined"
>
{isExporting ? "Exporting..." : "Export"}
</Button>
<Menu
anchorEl={exportAnchorEl}
id="cases-page-export-menu"
onClose={handleExportClose}
open={Boolean(exportAnchorEl)}
>
<MenuItem onClick={() => void handleExport("csv")}>Export to CSV</MenuItem>
<MenuItem onClick={() => void handleExport("pdf")}>Export to PDF</MenuItem>
</Menu>
</Box>

{/* Filtered-results hint */}
Expand Down
Loading