Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
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
@@ -0,0 +1,92 @@
// Copyright (c) 2026 WSO2 LLC. (https://www.wso2.com).
//
// WSO2 LLC. licenses this file to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file except
// in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

import {
FormControl,
Grid,
InputLabel,
MenuItem,
Select,
Typography,
} from "@wso2/oxygen-ui";
import type { JSX } from "react";
import type { SelectChangeEvent } from "@wso2/oxygen-ui";
import type { FilterField } from "@components/common/filter-panel/FilterPopover";

/**
* Select-specific FilterField that ensures only select-type fields are passed.
*/
type SelectFilterField = FilterField & { type: "select" };

export interface CasesFiltersProps {
filters: Record<string, string | number>;
filterFields: SelectFilterField[];
onFilterChange: (field: string, value: string | number) => void;
}

/**
* CasesFilters component to display filter dropdowns.
*
* @param {CasesFiltersProps} props - Filter values, metadata, and change handler.
* @returns {JSX.Element} The rendered filter dropdowns.
*/
export default function CasesFilters({
filters,
filterFields,
onFilterChange,
}: CasesFiltersProps): JSX.Element {
const handleSelectChange =
(field: string) => (event: SelectChangeEvent<string | number>) => {
const val = event.target.value;
onFilterChange(field, val);
};

return (
<Grid container spacing={2} sx={{ mt: 1 }}>
{filterFields.map((field) => {
const options = field.options || [];

return (
<Grid key={field.id} size={{ xs: 12, sm: 6, md: 2.4 }}>
<FormControl fullWidth size="small">
<InputLabel id={`${field.id}-label`}>{field.label}</InputLabel>
<Select
labelId={`${field.id}-label`}
id={field.id}
value={filters[field.id] || ""}
label={field.label}
onChange={handleSelectChange(field.id)}
>
<MenuItem value="">
<Typography variant="body2">All {field.label}</Typography>
</MenuItem>
{options.map((option) => {
const label = typeof option === "string" ? option : option.label;
const value = typeof option === "string" ? option : option.value;
return (
<MenuItem key={value} value={value}>
<Typography variant="body2">{label}</Typography>
</MenuItem>
);
})}
</Select>
</FormControl>
</Grid>
);
})}
</Grid>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,17 @@
// specific language governing permissions and limitations
// under the License.

import { ListingTable } from "@wso2/oxygen-ui";
import { type JSX, useState, useMemo, useCallback, useEffect } from "react";
import { ListingTable, Divider, Box } from "@wso2/oxygen-ui";
import { type JSX, useState, useMemo, useEffect } from "react";
import { useNavigate } from "react-router";
import { useAsgardeo } from "@asgardeo/react";
import { getNoveraChatEnabled } from "@utils/settingsStorage";
import useGetProjectCases from "@api/useGetProjectCases";
import { useGetProjectCasesPage } from "@api/useGetProjectCasesPage";
import useGetProjectFilters from "@api/useGetProjectFilters";
import { useGetDeployments } from "@api/useGetDeployments";
import FilterPopover, {
type FilterField,
} from "@components/common/filter-panel/FilterPopover";
import type { FilterField } from "@components/common/filter-panel/FilterPopover";
import CasesTableHeader from "@components/dashboard/cases-table/CasesTableHeader";
import CasesFilters from "@components/dashboard/cases-table/CasesFilters";
import CasesList from "@components/dashboard/cases-table/CasesList";
import {
normalizeCaseTypeOptions,
Expand Down Expand Up @@ -58,8 +56,6 @@ const CasesTable = ({

const {
data: filtersMetadata,
isFetching: isFetchingFilters,
isError: isErrorFilters,
} = useGetProjectFilters(projectId);

// Fetch deployments for the deployment filter
Expand Down Expand Up @@ -199,10 +195,9 @@ const CasesTable = ({
: pageQuery.isFetching;
const isError = showAll ? infiniteQuery.isError : pageQuery.isError;

const handleFilterSearch = (newFilters: Record<string, any>) => {
setFilters(newFilters);
const handleClearFilters = () => {
setFilters({});
setPage(0);
setShowAll(false);
setIsLoadingAll(false);
};

Expand All @@ -216,28 +211,6 @@ const CasesTable = ({
setIsLoadingAll(false);
};

const handleRemoveFilter = (field: string) => {
const newFilters = { ...filters };
delete newFilters[field];
setFilters(newFilters);
setPage(0);
setShowAll(false);
setIsLoadingAll(false);
};

const handleClearFilters = () => {
setFilters({});
setPage(0);
setIsLoadingAll(false);
};

const handleAllCases = () => {
setShowAll(true);
setIsLoadingAll(true);
setFilters({});
setPage(0);
};

const handleChangePage = (_event: unknown, newPage: number) => {
setPage(newPage);
};
Expand All @@ -249,59 +222,39 @@ const CasesTable = ({
setPage(0);
};

const activeFilterFields = dynamicFilterFields.map((field) => ({
id: field.id,
label: field.label,
options: field.options,
}));

const handleCreateCase = useCallback(() => {
if (getNoveraChatEnabled()) {
navigate(`/${projectId}/support/chat/describe-issue`);
} else {
navigate(`/${projectId}/support/chat/create-case`, {
state: { skipChat: true },
});
}
}, [navigate, projectId]);

const getDisplayValue = (fieldId: string, value: any): string => {
if (!value) return "";
const field = dynamicFilterFields.find((f) => f.id === fieldId);
if (field?.options) {
const option = field.options.find((opt) =>
typeof opt === "string" ? opt === value : opt.value === value,
);
if (option) {
return typeof option === "string" ? option : option.label;
}
}
return String(value);
const getActiveFiltersCount = () => {
return Object.values(filters).filter((v) => v !== "" && v != null).length;
};

const mappedAppliedFilters = Object.entries(filters).reduce(
(acc, [key, value]) => {
acc[key] = getDisplayValue(key, value);
return acc;
},
{} as Record<string, string>,
);

return (
<ListingTable.Container sx={{ width: "100%", mb: 4, p: 3 }}>
{/* Header */}
<CasesTableHeader
activeFiltersCount={Object.keys(filters).length}
appliedFilters={mappedAppliedFilters}
filterFields={activeFilterFields}
onRemoveFilter={handleRemoveFilter}
onUpdateFilter={handleUpdateFilter}
onClearAll={handleClearFilters}
onFilterClick={() => setIsFilterOpen(true)}
onAllCases={handleAllCases}
onCreateCase={handleCreateCase}
activeFiltersCount={getActiveFiltersCount()}
isFiltersOpen={isFilterOpen}
onFilterToggle={() => {
if (getActiveFiltersCount() > 0) {
handleClearFilters();
} else {
setIsFilterOpen(!isFilterOpen);
}
}}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
dileepapeiris marked this conversation as resolved.
/>

{/* Filter dropdowns section */}
{isFilterOpen && (
<>
<Divider sx={{ my: 2 }} />
<Box sx={{ mb: 3 }}>
<CasesFilters
filters={filters}
filterFields={dynamicFilterFields}
onFilterChange={handleUpdateFilter}
/>
</Box>
</>
)}

{/* Cases list */}
<CasesList
isLoading={isFetchingCases || isAuthLoading}
Expand All @@ -314,18 +267,6 @@ const CasesTable = ({
onCaseClick={(c) => navigate(`/${projectId}/support/cases/${c.id}`)}
showPagination={!showAll}
/>

{/* Filter popover */}
<FilterPopover
open={isFilterOpen}
onClose={() => setIsFilterOpen(false)}
onSearch={handleFilterSearch}
initialFilters={filters}
fields={dynamicFilterFields}
title="Filter Cases"
isLoading={isFetchingFilters || isAuthLoading}
isError={isErrorFilters}
/>
</ListingTable.Container>
);
};
Expand Down
Loading