Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
31 changes: 26 additions & 5 deletions src/pages/main-page/playbooks-page/PlaybooksPage.styles.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,39 @@
import styled from "styled-components";

export const PlaybookListItem = styled.div`
position: relative;

display: flex;
align-items: center;
gap: ${({ theme }) => theme.spacing["2xl"]};

padding-left: ${({ theme }) => theme.spacing.lg};
padding-right: ${({ theme }) => theme.spacing.lg};

border: 2px solid ${({ theme }) => theme.colors.palette.primary};
border-radius: ${({ theme }) => theme.radius.lg};
border: 1px solid ${({ theme }) => theme.colors.border.medium};
border-radius: ${({ theme }) => `0 ${theme.radius.sm} ${theme.radius.sm} 0`};
background-color: ${({ theme }) => theme.colors.background.primary};

&::before {
content: "";
position: absolute;
top: -1px;
bottom: -1px;
left: -1px;

width: 4px;

background-color: ${({ theme }) => theme.colors.palette.primary};
}

cursor: pointer;
transition:
background-color ${({ theme }) => theme.transitions.base},
border-color ${({ theme }) => theme.transitions.base},
box-shadow ${({ theme }) => theme.transitions.base};
border-color ${({ theme }) => theme.transitions.base};

&:hover {
border-color: ${({ theme }) => theme.colors.info.border};
background-color: ${({ theme }) => theme.colors.info.bg};
box-shadow: ${({ theme }) => theme.shadows.md};
}
`;

Expand Down Expand Up @@ -144,6 +156,15 @@ export const PlaybookActions = styled.div`
padding-left: ${({ theme }) => theme.spacing.md};
`;

export const HeaderActions = styled.div`
display: flex;
align-items: center;
gap: ${({ theme }) => theme.spacing.md};

width: 100%;
max-width: 400px;
`;

export const PlaybookList = styled.div`
display: flex;
flex-direction: column;
Expand Down
44 changes: 32 additions & 12 deletions src/pages/main-page/playbooks-page/PlaybooksPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ThemeSize, ThemeVariant } from "@/components";
import { useQuery } from "@tanstack/react-query";
import { IconFilePlus } from "@tabler/icons-react";
import { useQuery } from "@tanstack/react-query";
import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router";

Expand All @@ -14,13 +14,16 @@ import {
CardTitle,
CenteredCardContent,
Icon,
Input,
Spinner,
Text,
} from "@/components";
import { Playbook, Step } from "@/types";
import { PATHS, sortByString } from "@/utils";

import { PlaybookActionsMenu } from "./PlaybookActionsMenu";
import {
HeaderActions,
PlaybookActions,
PlaybookDescription,
PlaybookInfo,
Expand All @@ -34,8 +37,7 @@ import {
TimelineStep,
TimelineSteps,
} from "./PlaybooksPage.styles";
import { getOrderedSteps } from "./utils";
import { PlaybookActionsMenu } from "./PlaybookActionsMenu";
import { filterPlaybooks, getOrderedSteps } from "./utils";

interface PlaybookTimelineProps {
workflow: Record<string, Step>;
Expand Down Expand Up @@ -135,14 +137,15 @@ const PlaybookItem: React.FC<PlaybookItemProps> = ({ playbook, onClick }) => {

export const PlaybooksPage: React.FC = () => {
const navigate = useNavigate();
const [search, setSearch] = useState("");
const { data, isLoading, isError, error } = useQuery({
queryKey: ["playbooks"],
queryFn: getPlaybooks,
refetchOnWindowFocus: false,
});

const sortedPlaybooks = sortByString(
[...(data || [])],
[...filterPlaybooks(data || [], search)],
(p) => p.name || "",
true,
false,
Expand All @@ -164,7 +167,13 @@ export const PlaybooksPage: React.FC = () => {
if (isError)
return <Text>{parsedError?.message || "Could not load playbooks"}</Text>;
if (sortedPlaybooks.length === 0)
return <Text>No playbooks available</Text>;
return (
<Text>
{search.trim()
? `No playbooks match "${search.trim()}"`
: "No playbooks available"}
</Text>
);
return (
<PlaybookList>
{sortedPlaybooks.map((playbook) => (
Expand All @@ -182,13 +191,24 @@ export const PlaybooksPage: React.FC = () => {
<CardContainer>
<CardHeader>
<CardTitle>Playbooks</CardTitle>
<Button
$variant={ThemeVariant.Primary}
onClick={() => navigate(PATHS.PLAYBOOKS.NEW)}
>
<Icon $icon={IconFilePlus} />
New
</Button>
<HeaderActions>
<Input
id="playbooks-search"
type="search"
placeholder="Search by name, ID or type"
aria-label="Search playbooks"
value={search}
autoFocus
onChange={(e) => setSearch(e.target.value)}
/>
<Button
$variant={ThemeVariant.Primary}
onClick={() => navigate(PATHS.PLAYBOOKS.NEW)}
>
<Icon $icon={IconFilePlus} />
New
</Button>
</HeaderActions>
</CardHeader>
<CardBody>{renderBody()}</CardBody>
</CardContainer>
Expand Down
16 changes: 16 additions & 0 deletions src/pages/main-page/playbooks-page/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,22 @@ export const getOrderedSteps = (
return steps;
};

/**
* Case-insensitive search on name, id and playbook types.
*/
export const filterPlaybooks = (
playbooks: Playbook[],
query: string,
): Playbook[] => {
const q = query.trim().toLowerCase();
if (!q) return playbooks;
return playbooks.filter((p) =>
[p.name, p.id, ...(p.playbook_types ?? [])].some((field) =>
field?.toLowerCase().includes(q),
),
);
};

/**
* Generates a safe filename for exporting a playbook.
* Format: {id}-{modified-date}.json
Expand Down
Loading