Skip to content
Open
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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ This project was bootstrapped with [Create React App](https://github.com/faceboo

In the project directory, you can run:

### `npm install`
Installs the dependencies required

### `npm start`

Runs the app in the development mode.\
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added public/assets/icons/Check-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
57 changes: 57 additions & 0 deletions src/components/ColumnButton.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import ColumnButton from "./ColumnButton";

// Mock React-Bootstrap components
jest.mock("react-bootstrap", () => ({
Button: ({ children, ...props }: any) => <button {...props}>{children}</button>,
Tooltip: ({ id, children }: any) => <div id={id}>{children}</div>,
OverlayTrigger: ({ children, overlay }: any) => (
<div>
{overlay}
{children}
</div>
),
}));

describe("ColumnButton", () => {
const mockOnClick = jest.fn();

const baseProps = {
id: "test-button",
label: "Test Button",
tooltip: "This is a test tooltip",
variant: "primary",
size: "sm" as const,
className: "custom-class",
onClick: mockOnClick,
icon: <span data-testid="icon">Icon</span>,
};

test("should call onClick when the button is clicked", async () => {
render(<ColumnButton {...baseProps} tooltip={undefined} />);
const button = screen.getByRole("button", { name: "Test Button" });

userEvent.click(button);
expect(mockOnClick).toHaveBeenCalledTimes(1);
});

test("should render a tooltip when tooltip is provided", () => {
render(<ColumnButton {...baseProps} />);
const tooltip = screen.getByText("This is a test tooltip");

// Vanilla assertion to check if tooltip is rendered
expect(tooltip).not.toBeNull();
const button = screen.getByRole("button", { name: "Test Button" });
expect(button).not.toBeNull(); // Ensure the button is still present
});

test("should not render a tooltip when tooltip is not provided", () => {
render(<ColumnButton {...baseProps} tooltip={undefined} />);
const tooltip = screen.queryByText("This is a test tooltip");

// Vanilla assertion to check if tooltip is not rendered
expect(tooltip).toBeNull();
});
});
57 changes: 57 additions & 0 deletions src/components/ColumnButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from "react";
import { Button, OverlayTrigger, Tooltip } from "react-bootstrap";

/**
* @author Rutvik Kulkarni on Nov, 2024
*/

interface ColumnButtonProps {
id: string;
label?: string;
tooltip?: string;
variant: string;
size?: "sm" | "lg"; // Matches React-Bootstrap Button prop
className?: string;
onClick: () => void;
icon: React.ReactNode;
}

const ColumnButton: React.FC<ColumnButtonProps> = (props) => {
const {
id,
label,
tooltip,
variant,
size,
className,
onClick,
icon,
} = props;

const displayButton = (
<Button
variant={variant}
size={size}
className={className}
onClick={onClick}
aria-label={label}
>
{icon}
</Button>
);

if (tooltip) {
return (
<OverlayTrigger
placement="top"
overlay={<Tooltip id={`${id}-tooltip`}>{tooltip}</Tooltip>}
>
{displayButton}
</OverlayTrigger>
);
}

return displayButton;
};

export default ColumnButton;
1 change: 1 addition & 0 deletions src/components/Form/FormCheckBoxGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const FormCheckboxGroup: React.FC<IFormPropsWithOption> = (props) => {
<Form.Check
{...field}
key={option.value}
id={`${controlId}-${option.value}`}
type="checkbox"
className="mx-md-2"
label={option.label}
Expand Down
7 changes: 7 additions & 0 deletions src/components/Table/Pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface PaginationProps {
setPageSize: (pageSize: number) => void;
getPageCount: () => number;
getState: () => TableState;
totalItems: number;
}

const Pagination: React.FC<PaginationProps> = (props) => {
Expand All @@ -29,7 +30,12 @@ const Pagination: React.FC<PaginationProps> = (props) => {
setPageSize,
getPageCount,
getState,
totalItems,
} = props;
const pageSize = getState().pagination.pageSize;
if (totalItems <= pageSize) {
return null;
}
return (
<Row className="justify-content-center">
<Col xs="auto">
Expand Down Expand Up @@ -69,6 +75,7 @@ const Pagination: React.FC<PaginationProps> = (props) => {
{ label: "Show 10", value: "10" },
{ label: "Show 25", value: "25" },
{ label: "Show 50", value: "50" },
{ label: "Show All", value: String(totalItems) },
]}
input={{
value: getState().pagination.pageSize,
Expand Down
24 changes: 16 additions & 8 deletions src/components/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {
import GlobalFilter from "./GlobalFilter";
import Pagination from "./Pagination";
import RowSelectCheckBox from "./RowSelectCheckBox";
import { FaSearch } from "react-icons/fa";

interface TableProps {
data: Record<string, any>[];
Expand Down Expand Up @@ -78,6 +77,7 @@ import {
</button>
);
},
size: 40,
enableSorting: false,
enableColumnFilter: false,
};
Expand All @@ -103,6 +103,7 @@ import {
}}
/>
),
size: 40,
enableSorting: false,
enableFilter: false,
}] : [];
Expand Down Expand Up @@ -134,6 +135,11 @@ import {
getPaginationRowModel: getPaginationRowModel(),
getExpandedRowModel: getExpandedRowModel(),
});

//Enable search filters for columns based on page size
const totalItems = initialData.length;
const pageSize = table.getState().pagination.pageSize;
const shouldShowColumnFilters = showColumnFilter && totalItems > pageSize;

const flatRows = table.getSelectedRowModel().flatRows;

Expand Down Expand Up @@ -166,21 +172,21 @@ import {
<GlobalFilter filterValue={globalFilter} setFilterValue={setGlobalFilter} />
)}
</Col>
<span style={{ marginLeft: "5px" }} onClick={toggleGlobalFilter}>
<FaSearch style={{ cursor: "pointer" }} />
{isGlobalFilterVisible ? " Hide" : " Show"}
</span>
{/* <span style={{ marginLeft: "5px" }} onClick={toggleGlobalFilter}>
{isGlobalFilterVisible ? " Hide" : " Show"}
</span> */}
</Row>
</Container>
<Container>
<Row>
<Col md={tableSize}>
<BTable striped hover responsive size="sm">
<BTable striped hover responsive size="sm" className="custom-table-layout">
<thead className="table-secondary">
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th key={header.id} colSpan={header.colSpan}>
<th key={header.id} colSpan={header.colSpan} style={{ width: `${header.getSize()}px` }}>
{header.isPlaceholder ? null : (
<>
<div
Expand All @@ -197,7 +203,7 @@ import {
desc: " 🔽",
}[header.column.getIsSorted() as string] ?? null}
</div>
{showColumnFilter && header.column.getCanFilter() ? (
{shouldShowColumnFilters && header.column.getCanFilter() ? (
<ColumnFilter column={header.column} />
) : null}
</>
Expand All @@ -224,6 +230,7 @@ import {
</td>
</tr>
)}

</React.Fragment>
))}
</tbody>
Expand All @@ -238,6 +245,7 @@ import {
setPageSize={table.setPageSize}
getPageCount={table.getPageCount}
getState={table.getState}
totalItems={initialData.length}
/>
)}
</Col>
Expand Down
60 changes: 60 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,64 @@
html {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
}

.custom-table-layout {
/* This forces the table to use a fixed layout algorithm. */
/* Column widths are determined by the table's width, not content. */
table-layout: fixed;
width: 100%;
}

.custom-table-layout th,
.custom-table-layout td {
/* This adds consistent, reasonable spacing inside all headers and cells. */
padding: 0.75rem 1rem;

/* This allows long text to wrap, preventing it from stretching the column. */
word-wrap: break-word;

/* This vertically aligns content in the middle, which looks cleaner. */
vertical-align: middle;
}

/* Make checkbox group readable on dark or busy backgrounds */
/* .checkbox-contrast .form-check {
background-color: #ffffff;
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 0.5rem;
padding: 0.5rem 0.75rem;
margin-bottom: 0.5rem;
} */

/* Bigger, clearer boxes */
.checkbox-contrast .form-check-input {
width: 1rem;
height: 1rem;
border: 1px solid black; /* Bootstrap primary */
box-shadow: none;
margin-top: 0.25rem; /* aligns with label text */
}

/* Solid “checked” state */
.checkbox-contrast .form-check-input:checked {
background-color: #0d6efd;
border-color: #0d6efd;
}

/* Make labels bold & darker for readability */
/* .checkbox-contrast .form-check-label {
font-weight: 700;
color: #0b1324;
} */

/* Optional: clearer focus outline for keyboard users */
/* .checkbox-contrast .form-check-input:focus {
outline: none;
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
} */

.custom-table-layout {
table-layout: auto;
width: 100%;
}
2 changes: 1 addition & 1 deletion src/pages/Authentication/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const Login: React.FC = () => {
.post("http://localhost:3002/login", values)
.then((response) => {
const payload = setAuthToken(response.data.token);

localStorage.setItem("session", JSON.stringify({ user: payload }));
dispatch(
authenticationActions.setAuthentication({
authToken: response.data.token,
Expand Down
Loading