0% found this document useful (0 votes)
3 views10 pages

Criteria Management Enhancement

The document is a React component for managing criteria, featuring a search bar, action buttons, and pagination. It utilizes TypeScript interfaces for type safety and includes mock data for demonstration. The component handles data fetching, filtering based on search terms, and displays results in a table format with options to run, edit, or delete criteria.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views10 pages

Criteria Management Enhancement

The document is a React component for managing criteria, featuring a search bar, action buttons, and pagination. It utilizes TypeScript interfaces for type safety and includes mock data for demonstration. The component handles data fetching, filtering based on search terms, and displays results in a table format with options to run, edit, or delete criteria.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 10

import { useState, useEffect, useMemo, FC } from "react";

import {
Typography,
InputBase,
Button,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
IconButton,
CircularProgress,
Box,
} from "@mui/material";
import {
Search as SearchIcon,
Delete as DeleteIcon,
Add as AddIcon,
KeyboardArrowDown as KeyboardArrowDownIcon,
EditNoteRounded,
InfoOutlined,
} from "@mui/icons-material";
import PlayArrowRoundedIcon from '@mui/icons-material/PlayArrowRounded';
import CustomButton from "../UI/CustomeButton.tsx";

// TypeScript interfaces
interface CriteriaItem {
id: number;
name: string;
companies: number;
shortlist: number;
expressions: number;
dateCreated: string;
createdBy: string;
}

interface SearchBarProps {
searchTerm: string;
onSearchChange: (value: string) => void;
onNewSearch: () => void;
}

interface ActionButtonsProps {
onRun: () => void;
onEdit: () => void;
onDelete: () => void;
}

interface PaginationProps {
currentPage: number;
totalPages: number;
onPageChange: (page: number) => void;
}

// Styles
const styles = {
typography: {
header: {
fontSize: "14px",
fontWeight: "600",
lineHeight: "22px",
letterSpacing: "0",
verticalAlign: "middle",
},
enterprise: {
fontWeight: "600",
color: "black",
textDecoration: "underline",
lineHeight: "22px",
fontSize: "14px",
letterSpacing: "0",
verticalAlign: "middle",
cursor: "pointer",
},
regular: {
fontWeight: "600",
color: "black",
lineHeight: "22px",
fontSize: "14px",
letterSpacing: "0",
},
creator: {
color: "black",
fontSize: "14px",
}
},
table: {
container: {
boxShadow: "none",
borderRadius: "10px",
border: "1px solid lightgray",
},
row: (isEven: boolean) => ({
backgroundColor: isEven ? "white" : "#f9f9f9",
}),
},
searchBar: {
container: {
p: "2px 4px",
boxShadow: "none",
borderRadius: "12px",
},
},
pagination: {
button: (isActive: boolean) => ({
minWidth: "32px",
height: "32px",
padding: 0,
borderRadius: "8px",
backgroundColor: isActive ? "#0a2559" : "white",
color: isActive ? "#fff" : "#666",
"&:hover": {
backgroundColor: isActive ? "#061a54" : "#f0f0f0",
},
}),
navButton: (disabled: boolean) => ({
color: disabled ? "#ccc" : "black",
backgroundColor: "white",
}),
},
actions: {
run: {
color: "rgba(16, 171, 78, 1)",
padding: "12px",
fontSize: "10px",
},
edit: {
color: "rgba(20, 45, 93, 1)",
fontWeight: "bold",
},
delete: {
color: "red",
fontWeight: "bold",
},
},
};

// Mock data - in a real app, this would come from an API


const MOCK_CRITERIA_DATA: CriteriaItem[] = [
{
id: 1,
name: "Retail> $10M Rev; $5M EBITDA; DSCR>2; ROA>2",
companies: 19,
shortlist: 15,
expressions: 10,
dateCreated: "Jan 10, 2023",
createdBy: "John Doe",
},
{
id: 2,
name: "Retail> $50M Rev; $3M EBITDA; DSCR>1; ROA>1",
companies: 25,
shortlist: 20,
expressions: 13,
dateCreated: "Jan 10, 2023",
createdBy: "John Doe",
},
{
id: 3,
name: "Retail> $20M Rev; $2M EBITDA; DSCR>2; ROA>2",
companies: 45,
shortlist: 35,
expressions: 30,
dateCreated: "Dec 20, 2022",
createdBy: "John Doe",
},
{
id: 4,
name: "Retail> $2M Rev; $1M EBITDA; DSCR>1; ROA>1",
companies: 12,
shortlist: 10,
expressions: 9,
dateCreated: "Nov 4, 2022",
createdBy: "John Doe",
},
];

// Reusable components
const SearchBar: FC<SearchBarProps> = ({ searchTerm, onSearchChange, onNewSearch })
=> (
<div className="flex mb-6 gap-4">
<Paper
component="form"
className="flex items-center w-full border"
sx={styles.searchBar.container}
elevation={0}
>
<IconButton className="p-2.5" aria-label="search">
<SearchIcon />
</IconButton>
<InputBase
className="ml-1 flex-1"
placeholder="Search"
value={searchTerm}
onChange={(e) => onSearchChange(e.target.value)}
/>
</Paper>
<CustomButton
className="font-bold rounded px-4 normal-case shadow-none whitespace-
nowrap"
onClick={onNewSearch}
>
<AddIcon />
NEW SEARCH
</CustomButton>
</div>
);

const ActionButtons: FC<ActionButtonsProps> = ({ onRun, onEdit, onDelete }) => (


<div className="flex justify-center">
<IconButton
size="large"
className="text-green-500"
sx={styles.actions.run}
onClick={onRun}
>
<PlayArrowRoundedIcon fontSize="large" />
</IconButton>

<IconButton
size="large"
sx={styles.actions.edit}
onClick={onEdit}
>
<EditNoteRounded />
</IconButton>

<IconButton
size="large"
className="text-red-500"
sx={styles.actions.delete}
onClick={onDelete}
>
<DeleteIcon />
</IconButton>
</div>
);

const Pagination: FC<PaginationProps> = ({ currentPage, totalPages, onPageChange })


=> {
// Generate page numbers to display
const pageNumbers = useMemo(() => {
const pages = [];
const maxVisiblePages = 3;

// Always show first few pages


for (let i = 1; i <= Math.min(maxVisiblePages, totalPages); i++) {
pages.push(i);
}

// Add last page if not already included


if (totalPages > maxVisiblePages && !pages.includes(totalPages)) {
pages.push(totalPages);
}

return pages;
}, [totalPages]);

return (
<div className="flex justify-end mt-4">
<div className="flex items-center gap-2">
<Button
disabled={currentPage === 1}
onClick={() => onPageChange(currentPage - 1)}
sx={styles.pagination.navButton(currentPage === 1)}
>
Prev
</Button>

{pageNumbers.map((pageNum, index) => {


// Add ellipsis between non-consecutive page numbers
if (index > 0 && pageNum - pageNumbers[index - 1] > 1) {
return (
<Box key={`ellipsis-${pageNum}`} sx={{ display: 'flex',
alignItems: 'center' }}>
<Typography
className="text-gray-600">...</Typography>
<Button
onClick={() => onPageChange(pageNum)}
sx={styles.pagination.button(currentPage ===
pageNum)}
>
{pageNum}
</Button>
</Box>
);
}

return (
<Button
key={pageNum}
onClick={() => onPageChange(pageNum)}
sx={styles.pagination.button(currentPage === pageNum)}
>
{pageNum}
</Button>
);
})}

<Button
disabled={currentPage === totalPages}
onClick={() => onPageChange(currentPage + 1)}
sx={styles.pagination.navButton(currentPage === totalPages)}
>
Next
</Button>
</div>
</div>
);
};

// Main component
export default function CriteriaManagement() {
const [page, setPage] = useState(1);
const [searchTerm, setSearchTerm] = useState("");
const [loading, setLoading] = useState(false);
const [criteriaData, setCriteriaData] = useState<CriteriaItem[]>([]);

// In a real app, this would be calculated based on total items and items per
page
const totalPages = 10;

// Simulate data fetching


useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 500));
setCriteriaData(MOCK_CRITERIA_DATA);
} catch (error) {
console.error("Error fetching criteria data:", error);
} finally {
setLoading(false);
}
};

fetchData();
}, []);

// Filter data based on search term


const filteredData = useMemo(() => {
if (!searchTerm.trim()) return criteriaData;

return criteriaData.filter(item =>


item.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
item.createdBy.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [criteriaData, searchTerm]);

// Handle actions
const handleNewSearch = () => {
console.log("Creating new search criteria");
// In a real app, this would navigate to a new page or open a modal
};

const handleRunCriteria = (id: number) => {


console.log(`Running criteria with ID: ${id}`);
};

const handleEditCriteria = (id: number) => {


console.log(`Editing criteria with ID: ${id}`);
};

const handleDeleteCriteria = (id: number) => {


console.log(`Deleting criteria with ID: ${id}`);
};

return (
<div className="p-6 max-w-full bg-gray-50 min-h-screen">
<h2 className="text-[30px] font-bold text-[#0a2559] leading-[40px]
tracking-normal mb-8">
Criteria Management
</h2>

<SearchBar
searchTerm={searchTerm}
onSearchChange={setSearchTerm}
onNewSearch={handleNewSearch}
/>

{loading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', my: 4 }}>
<CircularProgress />
</Box>
) : filteredData.length === 0 ? (
<Box sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
my: 4,
p: 4,
border: '1px solid #e0e0e0',
borderRadius: '10px',
backgroundColor: 'white'
}}>
<InfoOutlined sx={{ fontSize: 48, color: '#0a2559', mb: 2 }} />
<Typography variant="h6" sx={{ mb: 1 }}>No criteria
found</Typography>
<Typography variant="body2" color="textSecondary">
{searchTerm ? 'Try adjusting your search term' : 'Create a
new search to get started'}
</Typography>
</Box>
) : (
<TableContainer component={Paper} sx={styles.table.container}>
<Table className="min-w-[650px]">
<TableHead className="bg-gray-100">
<TableRow>
<TableCell className="py-3">
<div className="flex items-center">
<Typography className="text-black"
sx={styles.typography.header}>
Criteria Name
</Typography>
<KeyboardArrowDownIcon fontSize="small"
className="ml-1 text-gray-500"/>
</div>
</TableCell>
<TableCell align="center" className="py-3">
<Typography className="text-black"
sx={styles.typography.header}>
Companies
</Typography>
</TableCell>
<TableCell align="center" className="py-3">
<Typography className="text-black"
sx={styles.typography.header}>
Shortlist
</Typography>
</TableCell>
<TableCell align="center" className="py-3">
<Typography className="text-black"
sx={styles.typography.header}>
Expressions of interest
</Typography>
</TableCell>
<TableCell className="py-3">
<div className="flex items-center">
<Typography className="text-black"
sx={styles.typography.header}>
Date created
</Typography>
<KeyboardArrowDownIcon fontSize="small"
className="ml-1 text-gray-500"/>
</div>
</TableCell>
<TableCell className="py-3">
<Typography className="text-black"
sx={styles.typography.header}>
Created By
</Typography>
</TableCell>
<TableCell align="center" className="py-3">
<Typography className="text-black"
sx={styles.typography.header}>
Actions
</Typography>
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{filteredData.map((row, index) => (
<TableRow
key={row.id}
sx={styles.table.row(index % 2 === 0)}
className="last:border-0"
>
<TableCell component="th" scope="row"
className="py-4">
<Typography className="text-blue-700"
sx={styles.typography.regular}>
{row.name}
</Typography>
</TableCell>
<TableCell align="center" className="py-4">
<Typography
sx={styles.typography.enterprise}>
{row.companies}
</Typography>
</TableCell>
<TableCell align="center" className="py-4">
<Typography
sx={styles.typography.enterprise}>
{row.shortlist}
</Typography>
</TableCell>
<TableCell align="center" className="py-4">
<Typography
sx={styles.typography.enterprise}>
{row.expressions}
</Typography>
</TableCell>
<TableCell className="py-4">
<Typography sx={styles.typography.regular}>
{row.dateCreated}
</Typography>
</TableCell>
<TableCell className="py-4">
<Typography sx={styles.typography.creator}>
{row.createdBy}
</Typography>
</TableCell>
<TableCell className="py-4">
<ActionButtons
onRun={() => handleRunCriteria(row.id)}
onEdit={() =>
handleEditCriteria(row.id)}
onDelete={() =>
handleDeleteCriteria(row.id)}
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}

{!loading && filteredData.length > 0 && (


<Pagination
currentPage={page}
totalPages={totalPages}
onPageChange={setPage}
/>
)}
</div>
);
}

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy