mirror of
https://github.com/Nezumi-2711/react-training.git
synced 2026-09-22 13:38:51 +00:00
Update table component
This commit is contained in:
@@ -1,70 +1,70 @@
|
|||||||
import { ReactNode, useContext } from 'react';
|
import { ColumnProps } from '@src/types/common';
|
||||||
|
|
||||||
// Styled
|
|
||||||
import {
|
import {
|
||||||
StyledBody,
|
StyledBody,
|
||||||
|
StyledFooter,
|
||||||
StyledHeader,
|
StyledHeader,
|
||||||
StyledRow,
|
StyledRow,
|
||||||
StyledTable,
|
StyledTable,
|
||||||
StyledFooter,
|
|
||||||
} from './styled';
|
} from './styled';
|
||||||
|
import Pagination from '../Pagination';
|
||||||
|
|
||||||
// Contexts
|
const parseWidthString = (columnsWidth: number[]): string => {
|
||||||
import TableContext from '@src/contexts/TableContext';
|
let result: string = '';
|
||||||
|
|
||||||
export interface ITable {
|
const totalWidth = columnsWidth.reduce((partialSum, a) => partialSum + a, 0);
|
||||||
columns?: string;
|
columnsWidth.forEach((width) => {
|
||||||
children: ReactNode;
|
result += Math.round((width / totalWidth) * 100).toString() + '% ';
|
||||||
}
|
});
|
||||||
|
|
||||||
export interface IHeader {
|
return result;
|
||||||
headerColumn: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ITableBody<T> {
|
|
||||||
data?: T[];
|
|
||||||
render?: CallbackMapFunc<T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
type CallbackMapFunc<T> = (value: T, index: number, array: T[]) => ReactNode;
|
|
||||||
|
|
||||||
const Table = ({ columns, children }: ITable) => {
|
|
||||||
return (
|
|
||||||
<TableContext.Provider value={{ columns }}>
|
|
||||||
<StyledTable>{children}</StyledTable>
|
|
||||||
</TableContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const Header = ({ headerColumn }: IHeader) => {
|
type Props<T> = {
|
||||||
const { columns } = useContext(TableContext);
|
columns: ColumnProps[];
|
||||||
|
rows: T[];
|
||||||
|
count?: number | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Table = <T,>({ rows, columns, count }: Props<T>) => {
|
||||||
|
const width = parseWidthString(columns.map((item) => item.width));
|
||||||
|
|
||||||
|
const headers = columns.map((column, index) => {
|
||||||
|
return <div key={`headCell-${index}`}>{column.title}</div>;
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderRows = rows.map((row, rowIndex) => {
|
||||||
|
return (
|
||||||
|
<StyledRow
|
||||||
|
key={`row-${rowIndex}`}
|
||||||
|
width={width}
|
||||||
|
onClick={
|
||||||
|
row[
|
||||||
|
'onClick' as keyof typeof row
|
||||||
|
] as React.MouseEventHandler<HTMLDivElement>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{columns.map((column, columnIndex) => {
|
||||||
|
const value = row[column.key as keyof typeof row] as string;
|
||||||
|
|
||||||
|
return <div key={`cell-${columnIndex}`}>{value}</div>;
|
||||||
|
})}
|
||||||
|
</StyledRow>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledHeader columns={columns}>
|
<StyledTable>
|
||||||
{headerColumn.map((item) => (
|
<StyledHeader width={width}>{headers}</StyledHeader>
|
||||||
<div key={item}>{item}</div>
|
|
||||||
))}
|
<StyledBody>{renderRows}</StyledBody>
|
||||||
</StyledHeader>
|
|
||||||
|
{Boolean(count) && (
|
||||||
|
<StyledFooter>
|
||||||
|
<Pagination count={count!} />
|
||||||
|
</StyledFooter>
|
||||||
|
)}
|
||||||
|
</StyledTable>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Body = <T,>({ data, render }: ITableBody<T>) => {
|
|
||||||
return (
|
|
||||||
data!.length && (
|
|
||||||
<StyledBody>{data?.map(render as CallbackMapFunc<T>)}</StyledBody>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Row = ({ children }: ITable) => {
|
|
||||||
const { columns } = useContext(TableContext);
|
|
||||||
|
|
||||||
return <StyledRow columns={columns}>{children}</StyledRow>;
|
|
||||||
};
|
|
||||||
|
|
||||||
Table.Header = Header;
|
|
||||||
Table.Body = Body;
|
|
||||||
Table.Row = Row;
|
|
||||||
Table.Footer = StyledFooter;
|
|
||||||
|
|
||||||
export default Table;
|
export default Table;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
// Interfaces
|
interface ICommonRow {
|
||||||
import { ITable } from '.';
|
width: string;
|
||||||
|
}
|
||||||
|
|
||||||
const StyledTable = styled.div`
|
const StyledTable = styled.div`
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
@@ -10,9 +11,9 @@ const StyledTable = styled.div`
|
|||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const CommonRow = styled.div<ITable>`
|
const CommonRow = styled.div<ICommonRow>`
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: ${(props) => props.columns};
|
grid-template-columns: ${(props) => props.width};
|
||||||
column-gap: 10px;
|
column-gap: 10px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-items: center;
|
justify-items: center;
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
import { useCallback } from 'react';
|
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import Menus from '@src/components/Menus';
|
|
||||||
import Table from '@src/components/Table';
|
import Table from '@src/components/Table';
|
||||||
import Message from '@src/components/Message';
|
import Message from '@src/components/Message';
|
||||||
import Search from '@src/components/Search';
|
import Search from '@src/components/Search';
|
||||||
import Pagination from '@src/components/Pagination';
|
|
||||||
import BookingRow from './BookingRow';
|
|
||||||
|
|
||||||
// Styled
|
// Styled
|
||||||
import { StyledOperationTable } from './styled';
|
import { StyledOperationTable } from './styled';
|
||||||
@@ -17,28 +12,57 @@ import Spinner from '@src/commons/styles/Spinner';
|
|||||||
import { useBookings } from '@src/hooks/bookings/useBookings';
|
import { useBookings } from '@src/hooks/bookings/useBookings';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { TBookingResponse } from '@src/types/booking';
|
import { ColumnProps } from '@src/types/common';
|
||||||
|
import { formatCurrency } from '@src/helpers/helper';
|
||||||
|
|
||||||
|
interface IBookingTable {
|
||||||
|
user: string;
|
||||||
|
date: string;
|
||||||
|
room: string;
|
||||||
|
amount: string;
|
||||||
|
status: string;
|
||||||
|
onClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
const BookingTable = () => {
|
const BookingTable = () => {
|
||||||
const columnName = [
|
const columns: ColumnProps[] = [
|
||||||
'User',
|
{
|
||||||
'Date',
|
key: 'user',
|
||||||
'Room',
|
title: 'User',
|
||||||
'Amount',
|
width: 15,
|
||||||
'Status'
|
},
|
||||||
|
{
|
||||||
|
key: 'date',
|
||||||
|
title: 'Date',
|
||||||
|
width: 25,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'room',
|
||||||
|
title: 'Room',
|
||||||
|
width: 20,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'amount',
|
||||||
|
title: 'Amount',
|
||||||
|
width: 15,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'status',
|
||||||
|
title: 'Status',
|
||||||
|
width: 15,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
const {
|
|
||||||
isLoading,
|
|
||||||
bookings,
|
|
||||||
count
|
|
||||||
} = useBookings();
|
|
||||||
|
|
||||||
const renderBookingRow = useCallback(
|
const { isLoading, bookings, count } = useBookings();
|
||||||
(booking: TBookingResponse) => (
|
|
||||||
<BookingRow booking={booking} key={booking.id} />
|
const tempBookings = bookings?.map((booking) => ({
|
||||||
),
|
date: 'Error',
|
||||||
[]
|
amount: formatCurrency(booking.amount),
|
||||||
);
|
room: booking.rooms!.name,
|
||||||
|
user: booking.users!.name,
|
||||||
|
status: booking.status ? 'Check in' : 'Check out',
|
||||||
|
onClick: () => console.log(booking),
|
||||||
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -49,19 +73,12 @@ const BookingTable = () => {
|
|||||||
|
|
||||||
{isLoading && <Spinner />}
|
{isLoading && <Spinner />}
|
||||||
|
|
||||||
{bookings && bookings.length ? (
|
{tempBookings && tempBookings.length ? (
|
||||||
<Menus>
|
<Table<IBookingTable>
|
||||||
<Table columns="15% 25% 20% 15% 10% 10% 5%">
|
columns={columns}
|
||||||
<Table.Header headerColumn={columnName} />
|
rows={tempBookings}
|
||||||
<Table.Body<TBookingResponse>
|
count={count}
|
||||||
data={bookings}
|
/>
|
||||||
render={renderBookingRow}
|
|
||||||
/>
|
|
||||||
<Table.Footer>
|
|
||||||
<Pagination count={count!} />
|
|
||||||
</Table.Footer>
|
|
||||||
</Table>
|
|
||||||
</Menus>
|
|
||||||
) : (
|
) : (
|
||||||
!isLoading && <Message>No data to show here!</Message>
|
!isLoading && <Message>No data to show here!</Message>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
import { useCallback } from 'react';
|
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import Menus from '@src/components/Menus';
|
|
||||||
import Table from '@src/components/Table';
|
import Table from '@src/components/Table';
|
||||||
import Message from '@src/components/Message';
|
import Message from '@src/components/Message';
|
||||||
import Search from '@src/components/Search';
|
import Search from '@src/components/Search';
|
||||||
import SortBy from '@src/components/SortBy';
|
import SortBy from '@src/components/SortBy';
|
||||||
import OrderBy from '@src/components/OrderBy';
|
import OrderBy from '@src/components/OrderBy';
|
||||||
import RoomRow from './RoomRow';
|
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { IRoom } from '@src/types/room';
|
import { IRoom } from '@src/types/room';
|
||||||
|
import { ColumnProps } from '@src/types/common';
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
import { ORDERBY_OPTIONS, ROOM_PAGE } from '@src/constants/commons';
|
import { ORDERBY_OPTIONS, ROOM_PAGE } from '@src/constants/commons';
|
||||||
@@ -22,20 +19,44 @@ import Spinner from '@src/commons/styles/Spinner';
|
|||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
import { useRooms } from '@src/hooks/rooms/useRooms';
|
import { useRooms } from '@src/hooks/rooms/useRooms';
|
||||||
import Pagination from '@src/components/Pagination';
|
|
||||||
|
interface IRoomTable extends Omit<IRoom, 'status'>{
|
||||||
|
status: string;
|
||||||
|
onClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
const RoomTable = () => {
|
const RoomTable = () => {
|
||||||
const columnName = ['Id', 'Name', 'Price', 'Status'];
|
const columns: ColumnProps[] = [
|
||||||
const {
|
{
|
||||||
isLoading,
|
key: 'id',
|
||||||
rooms,
|
title: 'Id',
|
||||||
count
|
width: 10,
|
||||||
} = useRooms();
|
},
|
||||||
|
{
|
||||||
|
key: 'name',
|
||||||
|
title: 'Name',
|
||||||
|
width: 40,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'price',
|
||||||
|
title: 'Price',
|
||||||
|
width: 20,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'status',
|
||||||
|
title: 'Status',
|
||||||
|
width: 20,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const { isLoading, rooms, count } = useRooms();
|
||||||
|
|
||||||
const renderRoomRow = useCallback(
|
const tempRooms = rooms?.map((room) => ({
|
||||||
(room: IRoom) => <RoomRow room={room} key={room.id} />,
|
...room,
|
||||||
[]
|
status: room.status
|
||||||
);
|
? 'Unavailable'
|
||||||
|
: 'Available',
|
||||||
|
onClick: () => console.log(room),
|
||||||
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -49,16 +70,8 @@ const RoomTable = () => {
|
|||||||
|
|
||||||
{isLoading && <Spinner />}
|
{isLoading && <Spinner />}
|
||||||
|
|
||||||
{rooms && rooms.length ? (
|
{tempRooms && tempRooms.length ? (
|
||||||
<Menus>
|
<Table<IRoomTable> columns={columns} rows={tempRooms} count={count}/>
|
||||||
<Table columns="10% 40% 20% 20% 5%">
|
|
||||||
<Table.Header headerColumn={columnName} />
|
|
||||||
<Table.Body<IRoom> data={rooms} render={renderRoomRow} />
|
|
||||||
<Table.Footer>
|
|
||||||
<Pagination count={count!}/>
|
|
||||||
</Table.Footer>
|
|
||||||
</Table>
|
|
||||||
</Menus>
|
|
||||||
) : (
|
) : (
|
||||||
!isLoading && <Message>No data to show here!</Message>
|
!isLoading && <Message>No data to show here!</Message>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,17 +1,9 @@
|
|||||||
import { useCallback } from 'react';
|
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import Menus from '@src/components/Menus';
|
|
||||||
import Table from '@src/components/Table';
|
import Table from '@src/components/Table';
|
||||||
import Message from '@src/components/Message';
|
import Message from '@src/components/Message';
|
||||||
import Search from '@src/components/Search';
|
import Search from '@src/components/Search';
|
||||||
import SortBy from '@src/components/SortBy';
|
import SortBy from '@src/components/SortBy';
|
||||||
import OrderBy from '@src/components/OrderBy';
|
import OrderBy from '@src/components/OrderBy';
|
||||||
import UserRow from './UserRow';
|
|
||||||
import Pagination from '@src/components/Pagination';
|
|
||||||
|
|
||||||
// Types
|
|
||||||
import { IUser } from '@src/types/user';
|
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
import { ORDERBY_OPTIONS, USER_PAGE } from '@src/constants/commons';
|
import { ORDERBY_OPTIONS, USER_PAGE } from '@src/constants/commons';
|
||||||
@@ -23,29 +15,46 @@ import Spinner from '@src/commons/styles/Spinner';
|
|||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
import { useUsers } from '@src/hooks/users/useUsers';
|
import { useUsers } from '@src/hooks/users/useUsers';
|
||||||
|
import { ColumnProps } from '@src/types/common';
|
||||||
|
import { IUser } from '@src/types/user';
|
||||||
|
|
||||||
|
interface IUserTable extends Omit<IUser, 'isBooked'>{
|
||||||
|
isBooked: string;
|
||||||
|
onClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
const UserTable = () => {
|
const UserTable = () => {
|
||||||
const columnName = [
|
const columns: ColumnProps[] = [
|
||||||
'Id',
|
{
|
||||||
'Name',
|
key: 'id',
|
||||||
'Phone',
|
title: 'Id',
|
||||||
'Is Booked'
|
width: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'name',
|
||||||
|
title: 'Name',
|
||||||
|
width: 35,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'phone',
|
||||||
|
title: 'Phone',
|
||||||
|
width: 30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'isBooked',
|
||||||
|
title: 'Is Booked',
|
||||||
|
width: 20,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
const {
|
const { isLoading, users, count } = useUsers();
|
||||||
isLoading,
|
|
||||||
users,
|
|
||||||
count
|
|
||||||
} = useUsers();
|
|
||||||
|
|
||||||
const renderUserRow = useCallback(
|
const tempUsers = users?.map((user) => ({
|
||||||
(user: IUser) => (
|
...user,
|
||||||
<UserRow
|
isBooked: user.isBooked
|
||||||
user={user}
|
? 'Yes'
|
||||||
key={user.id}
|
: 'No',
|
||||||
/>
|
onClick: () => console.log(user),
|
||||||
),
|
}));
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -59,16 +68,8 @@ const UserTable = () => {
|
|||||||
|
|
||||||
{isLoading && <Spinner />}
|
{isLoading && <Spinner />}
|
||||||
|
|
||||||
{users && users.length ? (
|
{tempUsers && tempUsers.length ? (
|
||||||
<Menus>
|
<Table<IUserTable> columns={columns} rows={tempUsers} count={count}/>
|
||||||
<Table columns="10% 35% 30% 15% 5%">
|
|
||||||
<Table.Header headerColumn={columnName} />
|
|
||||||
<Table.Body<IUser> data={users} render={renderUserRow} />
|
|
||||||
<Table.Footer>
|
|
||||||
<Pagination count={count!}/>
|
|
||||||
</Table.Footer>
|
|
||||||
</Table>
|
|
||||||
</Menus>
|
|
||||||
) : (
|
) : (
|
||||||
!isLoading && <Message>No data to show here!</Message>
|
!isLoading && <Message>No data to show here!</Message>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -12,6 +12,18 @@ interface ILogin {
|
|||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ColumnProps {
|
||||||
|
key: string;
|
||||||
|
title: string;
|
||||||
|
width: number;
|
||||||
|
}
|
||||||
|
|
||||||
type TDirection = 'vertical' | 'horizontal';
|
type TDirection = 'vertical' | 'horizontal';
|
||||||
|
|
||||||
export type { Nullable, IDataState, TDirection, ILogin };
|
export type {
|
||||||
|
Nullable,
|
||||||
|
IDataState,
|
||||||
|
TDirection,
|
||||||
|
ILogin,
|
||||||
|
ColumnProps
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user