forked from cerc-io/snowballtools-base
Filter deployments with status options (#15)
This commit is contained in:
parent
cfb299c79e
commit
318ebdfd26
@ -1,16 +1,10 @@
|
|||||||
import React, { ChangeEventHandler, forwardRef } from 'react';
|
import React, { forwardRef } from 'react';
|
||||||
|
|
||||||
import { Input } from '@material-tailwind/react';
|
import { Input, InputProps } from '@material-tailwind/react';
|
||||||
|
|
||||||
interface SearchBarProps {
|
|
||||||
onChange?: ChangeEventHandler<HTMLInputElement>;
|
|
||||||
value?: string;
|
|
||||||
placeholder?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SearchBar: React.ForwardRefRenderFunction<
|
const SearchBar: React.ForwardRefRenderFunction<
|
||||||
HTMLInputElement,
|
HTMLInputElement,
|
||||||
SearchBarProps
|
InputProps
|
||||||
> = ({ value, onChange, placeholder = 'Search', ...props }, ref) => {
|
> = ({ value, onChange, placeholder = 'Search', ...props }, ref) => {
|
||||||
return (
|
return (
|
||||||
<div className="relative flex w-full gap-2">
|
<div className="relative flex w-full gap-2">
|
||||||
@ -28,8 +22,8 @@ const SearchBar: React.ForwardRefRenderFunction<
|
|||||||
}}
|
}}
|
||||||
// TODO: Debug issue: https://github.com/creativetimofficial/material-tailwind/issues/427
|
// TODO: Debug issue: https://github.com/creativetimofficial/material-tailwind/issues/427
|
||||||
crossOrigin={undefined}
|
crossOrigin={undefined}
|
||||||
ref={ref}
|
|
||||||
{...props}
|
{...props}
|
||||||
|
ref={ref}
|
||||||
/>
|
/>
|
||||||
<div className="!absolute left-3 top-[13px]">
|
<div className="!absolute left-3 top-[13px]">
|
||||||
<i>^</i>
|
<i>^</i>
|
||||||
|
@ -1,60 +1,47 @@
|
|||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { Button } from '@material-tailwind/react';
|
import { Button, Typography } from '@material-tailwind/react';
|
||||||
|
|
||||||
import deploymentData from '../../../assets/deployments.json';
|
import deploymentData from '../../../assets/deployments.json';
|
||||||
import DeployDetailsCard from './DeploymentDetailsCard';
|
import DeployDetailsCard from './deployments/DeploymentDetailsCard';
|
||||||
import Dropdown from '../../Dropdown';
|
import FilterForm, { StatusOptions } from './deployments/FilterForm';
|
||||||
import SearchBar from '../../SearchBar';
|
|
||||||
|
|
||||||
const STATUS_OPTIONS = [
|
const DEFAULT_FILTER_VALUE = {
|
||||||
{ value: 'building', label: 'Building' },
|
searchedBranch: '',
|
||||||
{ value: 'ready', label: 'Ready' },
|
status: 'All status',
|
||||||
{ value: 'error', label: 'Error' },
|
};
|
||||||
];
|
|
||||||
|
|
||||||
const DeploymentsTabPanel = () => {
|
const DeploymentsTabPanel = () => {
|
||||||
const [searchedBranch, setSearchedBranch] = useState('');
|
const [filterValue, setFilterValue] = useState(DEFAULT_FILTER_VALUE);
|
||||||
|
|
||||||
const filteredDeployments = useMemo(() => {
|
const filteredDeployments = useMemo(() => {
|
||||||
if (searchedBranch) {
|
return deploymentData.filter((deployment) => {
|
||||||
return deploymentData.filter((deployment) =>
|
// Searched branch filter
|
||||||
deployment.branch.toLowerCase().includes(searchedBranch.toLowerCase()),
|
const branchMatch =
|
||||||
);
|
!filterValue.searchedBranch ||
|
||||||
}
|
deployment.branch
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(filterValue.searchedBranch.toLowerCase());
|
||||||
|
|
||||||
return deploymentData;
|
// Status filter
|
||||||
}, [searchedBranch]);
|
const statusMatch =
|
||||||
|
filterValue.status === StatusOptions.ALL_STATUS ||
|
||||||
|
deployment.status === filterValue.status;
|
||||||
|
|
||||||
|
return branchMatch && statusMatch;
|
||||||
|
});
|
||||||
|
}, [filterValue]);
|
||||||
|
|
||||||
const handleResetFilters = useCallback(() => {
|
const handleResetFilters = useCallback(() => {
|
||||||
setSearchedBranch('');
|
setFilterValue(DEFAULT_FILTER_VALUE);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<div className="grid grid-cols-4 gap-2 text-sm text-gray-600">
|
<FilterForm
|
||||||
<div className="col-span-2">
|
value={filterValue}
|
||||||
<SearchBar
|
onChange={(value) => setFilterValue(value)}
|
||||||
placeholder="Search branches"
|
|
||||||
value={searchedBranch}
|
|
||||||
onChange={(event) => setSearchedBranch(event.target.value)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div className="col-span-1">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="border border-gray-300 rounded p-2 w-full focus:border-blue-300 focus:outline-none focus:shadow-outline-blue"
|
|
||||||
placeholder="All time"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="col-span-1">
|
|
||||||
<Dropdown
|
|
||||||
placeholder="All status"
|
|
||||||
options={STATUS_OPTIONS}
|
|
||||||
onChange={() => {}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
{Boolean(filteredDeployments.length) ? (
|
{Boolean(filteredDeployments.length) ? (
|
||||||
filteredDeployments.map((deployment, key) => {
|
filteredDeployments.map((deployment, key) => {
|
||||||
@ -63,8 +50,10 @@ const DeploymentsTabPanel = () => {
|
|||||||
) : (
|
) : (
|
||||||
<div className="h-[50vh] bg-gray-100 flex rounded items-center justify-center">
|
<div className="h-[50vh] bg-gray-100 flex rounded items-center justify-center">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<h5 className="text-lg font-bold">No deployments found</h5>
|
<Typography variant="h5">No deployments found</Typography>
|
||||||
<p>Please change your search query or filters</p>
|
<Typography>
|
||||||
|
Please change your search query or filters
|
||||||
|
</Typography>
|
||||||
<Button
|
<Button
|
||||||
className="rounded-full mt-5"
|
className="rounded-full mt-5"
|
||||||
color="white"
|
color="white"
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
} from '@material-tailwind/react';
|
} from '@material-tailwind/react';
|
||||||
|
|
||||||
import { relativeTime } from '../../../utils/time';
|
import { relativeTime } from '../../../../utils/time';
|
||||||
|
|
||||||
interface DeploymentDetails {
|
interface DeploymentDetails {
|
||||||
title: string;
|
title: string;
|
@ -0,0 +1,77 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { Option, Select } from '@material-tailwind/react';
|
||||||
|
|
||||||
|
import SearchBar from '../../../SearchBar';
|
||||||
|
|
||||||
|
export enum StatusOptions {
|
||||||
|
ALL_STATUS = 'All status',
|
||||||
|
BUILDING = 'Building',
|
||||||
|
READY = 'Ready',
|
||||||
|
ERROR = 'Error',
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FilterValue {
|
||||||
|
searchedBranch: string;
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FilterFormProps {
|
||||||
|
value: FilterValue;
|
||||||
|
onChange: (value: FilterValue) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FilterForm = ({ value, onChange }: FilterFormProps) => {
|
||||||
|
const [searchedBranch, setSearchedBranch] = useState(value.searchedBranch);
|
||||||
|
const [selectedStatus, setSelectedStatus] = useState(value.status);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onChange({
|
||||||
|
searchedBranch,
|
||||||
|
status: selectedStatus,
|
||||||
|
});
|
||||||
|
}, [searchedBranch, selectedStatus]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSearchedBranch(value.searchedBranch);
|
||||||
|
setSelectedStatus(value.status);
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-4 gap-2 text-sm text-gray-600">
|
||||||
|
<div className="col-span-2">
|
||||||
|
<SearchBar
|
||||||
|
placeholder="Search branches"
|
||||||
|
value={searchedBranch}
|
||||||
|
onChange={(event) => setSearchedBranch(event.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-1">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="border border-gray-300 rounded p-2 w-full focus:border-blue-300 focus:outline-none focus:shadow-outline-blue"
|
||||||
|
placeholder="All time"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-1">
|
||||||
|
<Select
|
||||||
|
value={selectedStatus}
|
||||||
|
onChange={(value) => setSelectedStatus(value!)}
|
||||||
|
label="Select Version"
|
||||||
|
>
|
||||||
|
{Object.values(StatusOptions).map((status) => (
|
||||||
|
<Option
|
||||||
|
className={status === selectedStatus ? 'hidden' : ''}
|
||||||
|
key={status}
|
||||||
|
value={status}
|
||||||
|
>
|
||||||
|
^ {status}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FilterForm;
|
Loading…
Reference in New Issue
Block a user