Fleet UI: Add Software > FMA table - Add platform and status filter (#37805)
This commit is contained in:
@@ -0,0 +1 @@
|
||||
* Fleet UI: Add filtering by platform and add status to the Software > Add Fleet-maintained apps table
|
||||
+120
-8
@@ -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
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
+95
@@ -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>
|
||||
);
|
||||
};
|
||||
+13
@@ -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;
|
||||
}
|
||||
}
|
||||
+5
@@ -6,4 +6,9 @@
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
&__filters {
|
||||
display: flex;
|
||||
gap: $gap-table-elements;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user