Fleet UI: Add Software > FMA table - Add platform and status filter (#37805)

This commit is contained in:
RachelElysia
2026-02-04 09:16:11 -05:00
committed by GitHub
parent b1d0a5c2da
commit 4fdea91a7a
5 changed files with 234 additions and 8 deletions
+1
View File
@@ -0,0 +1 @@
* Fleet UI: Add filtering by platform and add status to the Software > Add Fleet-maintained apps table
@@ -1,4 +1,4 @@
import React, { useCallback, useMemo } from "react";
import React, { useCallback, useMemo, useState } from "react";
import { InjectedRouter } from "react-router";
import PATHS from "router/paths";
@@ -15,6 +15,12 @@ import TableCount from "components/TableContainer/TableCount";
import { ITableQueryData } from "components/TableContainer/TableContainer";
import EmptyTable from "components/EmptyTable";
import CustomLink from "components/CustomLink";
import {
FmaStatusFilter,
FmaPlatformFilter,
FmaPlatformValue,
FmaStatusValue,
} from "./FmaFilters/FmaFilters";
import { generateTableConfig } from "./FleetMaintainedAppsTableConfig";
@@ -95,6 +101,9 @@ const FleetMaintainedAppsTable = ({
orderKey,
currentPage,
}: IFleetMaintainedAppsTableProps) => {
const [status, setStatus] = useState<FmaStatusValue>("all");
const [platform, setPlatform] = useState<FmaPlatformValue>("all");
const determineQueryParamChange = useCallback(
(newTableQuery: ITableQueryData) => {
const changedEntry = Object.entries(newTableQuery).find(([key, val]) => {
@@ -117,13 +126,20 @@ const FleetMaintainedAppsTable = ({
);
const generateNewQueryParams = useCallback(
(newTableQuery: ITableQueryData, changedParam: string) => {
(
newTableQuery: ITableQueryData,
changedParam: string,
nextPlatform: FmaPlatformValue,
nextStatus: FmaStatusValue
) => {
const newQueryParam: Record<string, string | number | undefined> = {
query: newTableQuery.searchQuery,
team_id: teamId,
order_direction: newTableQuery.sortDirection,
order_key: newTableQuery.sortHeader,
page: changedParam === "pageIndex" ? newTableQuery.pageIndex : 0,
platform: nextPlatform === "all" ? undefined : nextPlatform,
status: nextStatus === "all" ? undefined : nextStatus,
};
return newQueryParam;
@@ -147,12 +163,23 @@ const FleetMaintainedAppsTable = ({
const newRoute = getNextLocationPath({
pathPrefix: PATHS.SOFTWARE_ADD_FLEET_MAINTAINED,
routeTemplate: "",
queryParams: generateNewQueryParams(newTableQuery, changedParam),
queryParams: generateNewQueryParams(
newTableQuery,
changedParam,
platform,
status
),
});
router.replace(newRoute);
},
[determineQueryParamChange, generateNewQueryParams, router]
[
determineQueryParamChange,
generateNewQueryParams,
router,
platform,
status,
]
);
const tableHeadersConfig = useMemo(() => {
@@ -166,17 +193,100 @@ const FleetMaintainedAppsTable = ({
const combinedAppsByPlatform =
(data && combineAppsByPlatform(data.fleet_maintained_apps ?? [])) ?? [];
const renderCount = () => {
if (!combinedAppsByPlatform) return null;
const filteredApps = combinedAppsByPlatform.filter((app) => {
const macAvailable = !!app.macos && !app.macos.software_title_id;
const winAvailable = !!app.windows && !app.windows.software_title_id;
return <TableCount name="items" count={combinedAppsByPlatform.length} />;
// platform filter
if (platform === "macos" && !app.macos) return false;
if (platform === "windows" && !app.windows) return false;
// status filter
if (status === "all") {
return true;
}
if (status === "available") {
if (platform === "macos") return macAvailable;
if (platform === "windows") return winAvailable;
return macAvailable || winAvailable;
}
return true;
});
const renderCount = () => {
if (!filteredApps) return null;
return <TableCount name="items" count={filteredApps.length} />;
};
const handleFmaStatusDropdownChange = (newStatus: FmaStatusValue) => {
setStatus(newStatus);
const newRoute = getNextLocationPath({
pathPrefix: PATHS.SOFTWARE_ADD_FLEET_MAINTAINED,
routeTemplate: "",
queryParams: generateNewQueryParams(
{
searchQuery: query,
sortDirection: orderDirection,
sortHeader: orderKey,
pageIndex: currentPage,
pageSize: perPage,
},
"status",
platform,
newStatus
),
});
router.replace(newRoute);
};
const handleFmaPlatformDropdownChange = (newPlatform: FmaPlatformValue) => {
setPlatform(newPlatform);
const newRoute = getNextLocationPath({
pathPrefix: PATHS.SOFTWARE_ADD_FLEET_MAINTAINED,
routeTemplate: "",
queryParams: generateNewQueryParams(
{
searchQuery: query,
sortDirection: orderDirection,
sortHeader: orderKey,
pageIndex: currentPage,
pageSize: perPage,
},
"platform",
newPlatform,
status
),
});
router.replace(newRoute);
};
const renderCustomControls = () => (
<div className={`${baseClass}__filters`}>
<FmaStatusFilter
value={status}
onChange={handleFmaStatusDropdownChange}
className={`${baseClass}__status-filter`}
/>
<FmaPlatformFilter
value={platform}
onChange={handleFmaPlatformDropdownChange}
className={`${baseClass}__platform-filter`}
/>
</div>
);
return (
<TableContainer<IRowProps>
className={baseClass}
columnConfigs={tableHeadersConfig}
data={combinedAppsByPlatform}
data={filteredApps}
isLoading={isLoading}
resultsTitle="items"
emptyComponent={EmptyFleetAppsTable}
@@ -193,6 +303,8 @@ const FleetMaintainedAppsTable = ({
inputPlaceHolder="Search by name"
onQueryChange={onQueryChange}
renderCount={renderCount}
customControl={renderCustomControls}
stackControls
/>
);
};
@@ -0,0 +1,95 @@
import React, { useMemo } from "react";
import { SingleValue } from "react-select-5";
import Slider from "components/forms/fields/Slider/Slider";
import DropdownWrapper, {
CustomOptionType,
} from "components/forms/fields/DropdownWrapper/DropdownWrapper";
const statusBaseClass = "fma-status-select";
const platformBaseClass = "fma-platform-select";
export type FmaPlatformValue = "all" | "macos" | "windows";
export type FmaStatusValue = "all" | "available";
interface IFmaPlatformFilterProps {
value: FmaPlatformValue;
onChange: (value: FmaPlatformValue) => void;
className?: string;
}
export const FmaPlatformFilter = ({
value,
onChange,
className,
}: IFmaPlatformFilterProps) => {
const options = useMemo<CustomOptionType[]>(() => {
return [
{
value: "all",
label: "All platforms",
isDisabled: false,
},
{
value: "macos",
label: "macOS",
isDisabled: false,
},
{
value: "windows",
label: "Windows",
isDisabled: false,
},
];
}, []);
const handleChange = (newValue: SingleValue<CustomOptionType>) => {
if (!newValue) return;
onChange(newValue.value as FmaPlatformValue);
};
return (
<div className={`${platformBaseClass} ${className || ""}`}>
<DropdownWrapper
name="fma-platform-filter"
options={options}
value={value}
onChange={handleChange}
variant="table-filter"
isSearchable={false}
placeholder="Filter by platform"
className={platformBaseClass}
iconName="filter-alt"
/>
</div>
);
};
interface IFmaStatusFilterProps {
value: FmaStatusValue;
onChange: (value: FmaStatusValue) => void;
className?: string;
}
export const FmaStatusFilter = ({
value,
onChange,
className,
}: IFmaStatusFilterProps) => {
const handleChange = () => {
onChange(value === "all" ? ("available" as FmaStatusValue) : "all");
};
const enabled = value === "available";
return (
<div className={`${statusBaseClass} ${className || ""}`}>
<Slider
onChange={handleChange}
value={enabled}
inactiveText="Hide added apps"
activeText="Hide added apps"
/>
</div>
);
};
@@ -0,0 +1,13 @@
.fma-platform-select {
width: 170px;
}
.fma-status-select {
width: max-content;
display: flex;
align-items: center;
.form-field {
align-items: end;
}
}
@@ -6,4 +6,9 @@
justify-content: center;
}
}
&__filters {
display: flex;
gap: $gap-table-elements;
}
}