diff --git a/src/features/alerts/components/tabs-alerts.tsx b/src/features/alerts/components/tabs-alerts.tsx
new file mode 100644
index 00000000..f5bf0523
--- /dev/null
+++ b/src/features/alerts/components/tabs-alerts.tsx
@@ -0,0 +1,118 @@
+import { useQueryGetWorkspaceAlerts } from "../hooks/use-query-get-workspace-alerts";
+import { isAlertMalicious } from "../lib/is-alert-malicious";
+import { multiFilter } from "@/lib/multi-filter";
+import { isAlertCritical } from "../lib/is-alert-critical";
+import { isAlertSecret } from "../lib/is-alert-secret";
+import { V1GetWorkspaceAlertsResponse } from "@/api/generated";
+import {
+ Tab as BaseTab,
+ Tabs,
+ TabList,
+ TabPanel,
+ Badge,
+ Card,
+ CardBody,
+} from "@stacklok/ui-kit";
+import {
+ AlertsFilterView,
+ useAlertsFilterSearchParams,
+} from "../hooks/use-alerts-filter-search-params";
+import { SearchFieldAlerts } from "./search-field-alerts";
+import { tv } from "tailwind-variants";
+
+type AlertsCount = {
+ all: number;
+ malicious: number;
+ secrets: number;
+};
+
+function select(data: V1GetWorkspaceAlertsResponse | undefined): AlertsCount {
+ const all: number = multiFilter(data, [isAlertCritical]).length;
+
+ const malicious: number = multiFilter(data, [
+ isAlertCritical,
+ isAlertMalicious,
+ ]).length;
+
+ const secrets: number = multiFilter(data, [
+ isAlertCritical,
+ isAlertSecret,
+ ]).length;
+
+ return {
+ all,
+ malicious,
+ secrets,
+ };
+}
+
+const tabStyle = tv({
+ base: [
+ "my-1 mx-0.5 first:ml-1 last:mr-1",
+ "rounded bg-transparent h-[calc(2rem-2px)] flex text-secondary items-center gap-1 !border-0",
+ "hover:bg-gray-50 hover:text-secondary",
+ "selected:bg-base hover:selected:bg-base selected:shadow-sm selected:border-gray-200 selected:text-secondary",
+ ],
+});
+
+function Tab({
+ id,
+ title,
+ count,
+}: {
+ title: string;
+ id: AlertsFilterView;
+ count: number;
+}) {
+ return (
+
+ {title}
+
+ {count}
+
+
+ );
+}
+
+export function TabsAlerts({ children }: { children: React.ReactNode }) {
+ const { state, setView } = useAlertsFilterSearchParams();
+
+ const { data } = useQueryGetWorkspaceAlerts({
+ select,
+ });
+
+ return (
+
setView(key.toString() as AlertsFilterView)}
+ selectedKey={state.view}
+ defaultSelectedKey={AlertsFilterView.ALL}
+ >
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+ );
+}
diff --git a/src/features/alerts/hooks/use-alerts-filter-search-params.ts b/src/features/alerts/hooks/use-alerts-filter-search-params.ts
index 6fe5c200..a6e700dc 100644
--- a/src/features/alerts/hooks/use-alerts-filter-search-params.ts
+++ b/src/features/alerts/hooks/use-alerts-filter-search-params.ts
@@ -10,14 +10,15 @@ export enum AlertsFilterView {
const alertsFilterSchema = z.object({
search: z.string().optional(),
- view: z.nativeEnum(AlertsFilterView),
+ view: z.nativeEnum(AlertsFilterView).optional().default(AlertsFilterView.ALL),
+ page: z.coerce.number().optional().default(0),
});
-type AlertsFilterSchema = z.output
;
+type AlertsFilterSchema = z.input;
-const DEFAULT_FILTER: AlertsFilterSchema = {
+const DEFAULT_FILTER = {
view: AlertsFilterView.ALL,
-};
+} as const satisfies AlertsFilterSchema;
export const useAlertsFilterSearchParams = () => {
const [searchParams, setSearchParams] = useSearchParams(
@@ -29,6 +30,8 @@ export const useAlertsFilterSearchParams = () => {
setSearchParams((prev) => {
if (view) prev.set("view", view);
if (!view) prev.delete("view");
+
+ prev.delete("page");
return prev;
});
},
@@ -46,7 +49,23 @@ export const useAlertsFilterSearchParams = () => {
[setSearchParams],
);
+ const nextPage = useCallback(() => {
+ setSearchParams((prev) => {
+ const page = Number(prev.get("page") ?? 0);
+ prev.set("page", (page + 1).toString());
+ return prev;
+ });
+ }, [setSearchParams]);
+
+ const prevPage = useCallback(() => {
+ setSearchParams((prev) => {
+ const page = Number(prev.get("page") ?? 0);
+ prev.set("page", (page - 1).toString());
+ return prev;
+ });
+ }, [setSearchParams]);
+
const state = alertsFilterSchema.parse(Object.fromEntries(searchParams));
- return { state, setView, setSearch };
+ return { state, setView, setSearch, nextPage, prevPage };
};
diff --git a/src/features/header/components/header.tsx b/src/features/header/components/header.tsx
index 40e11259..ce7f7c2e 100644
--- a/src/features/header/components/header.tsx
+++ b/src/features/header/components/header.tsx
@@ -37,7 +37,7 @@ export function Header({ hasError }: { hasError?: boolean }) {