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';
|
||||
|
||||
// Styled
|
||||
import { ColumnProps } from '@src/types/common';
|
||||
import {
|
||||
StyledBody,
|
||||
StyledFooter,
|
||||
StyledHeader,
|
||||
StyledRow,
|
||||
StyledTable,
|
||||
StyledFooter,
|
||||
} from './styled';
|
||||
import Pagination from '../Pagination';
|
||||
|
||||
// Contexts
|
||||
import TableContext from '@src/contexts/TableContext';
|
||||
const parseWidthString = (columnsWidth: number[]): string => {
|
||||
let result: string = '';
|
||||
|
||||
export interface ITable {
|
||||
columns?: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
const totalWidth = columnsWidth.reduce((partialSum, a) => partialSum + a, 0);
|
||||
columnsWidth.forEach((width) => {
|
||||
result += Math.round((width / totalWidth) * 100).toString() + '% ';
|
||||
});
|
||||
|
||||
export interface IHeader {
|
||||
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>
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
const Header = ({ headerColumn }: IHeader) => {
|
||||
const { columns } = useContext(TableContext);
|
||||
type Props<T> = {
|
||||
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 (
|
||||
<StyledHeader columns={columns}>
|
||||
{headerColumn.map((item) => (
|
||||
<div key={item}>{item}</div>
|
||||
))}
|
||||
</StyledHeader>
|
||||
<StyledTable>
|
||||
<StyledHeader width={width}>{headers}</StyledHeader>
|
||||
|
||||
<StyledBody>{renderRows}</StyledBody>
|
||||
|
||||
{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;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
// Interfaces
|
||||
import { ITable } from '.';
|
||||
interface ICommonRow {
|
||||
width: string;
|
||||
}
|
||||
|
||||
const StyledTable = styled.div`
|
||||
border: 1px solid var(--border-color);
|
||||
@@ -10,9 +11,9 @@ const StyledTable = styled.div`
|
||||
font-size: 20px;
|
||||
`;
|
||||
|
||||
const CommonRow = styled.div<ITable>`
|
||||
const CommonRow = styled.div<ICommonRow>`
|
||||
display: grid;
|
||||
grid-template-columns: ${(props) => props.columns};
|
||||
grid-template-columns: ${(props) => props.width};
|
||||
column-gap: 10px;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
// Components
|
||||
import Menus from '@src/components/Menus';
|
||||
import Table from '@src/components/Table';
|
||||
import Message from '@src/components/Message';
|
||||
import Search from '@src/components/Search';
|
||||
import Pagination from '@src/components/Pagination';
|
||||
import BookingRow from './BookingRow';
|
||||
|
||||
// Styled
|
||||
import { StyledOperationTable } from './styled';
|
||||
@@ -17,28 +12,57 @@ import Spinner from '@src/commons/styles/Spinner';
|
||||
import { useBookings } from '@src/hooks/bookings/useBookings';
|
||||
|
||||
// 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 columnName = [
|
||||
'User',
|
||||
'Date',
|
||||
'Room',
|
||||
'Amount',
|
||||
'Status'
|
||||
const columns: ColumnProps[] = [
|
||||
{
|
||||
key: 'user',
|
||||
title: 'User',
|
||||
width: 15,
|
||||
},
|
||||
{
|
||||
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(
|
||||
(booking: TBookingResponse) => (
|
||||
<BookingRow booking={booking} key={booking.id} />
|
||||
),
|
||||
[]
|
||||
);
|
||||
const { isLoading, bookings, count } = useBookings();
|
||||
|
||||
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 (
|
||||
<>
|
||||
@@ -49,19 +73,12 @@ const BookingTable = () => {
|
||||
|
||||
{isLoading && <Spinner />}
|
||||
|
||||
{bookings && bookings.length ? (
|
||||
<Menus>
|
||||
<Table columns="15% 25% 20% 15% 10% 10% 5%">
|
||||
<Table.Header headerColumn={columnName} />
|
||||
<Table.Body<TBookingResponse>
|
||||
data={bookings}
|
||||
render={renderBookingRow}
|
||||
{tempBookings && tempBookings.length ? (
|
||||
<Table<IBookingTable>
|
||||
columns={columns}
|
||||
rows={tempBookings}
|
||||
count={count}
|
||||
/>
|
||||
<Table.Footer>
|
||||
<Pagination count={count!} />
|
||||
</Table.Footer>
|
||||
</Table>
|
||||
</Menus>
|
||||
) : (
|
||||
!isLoading && <Message>No data to show here!</Message>
|
||||
)}
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
// Components
|
||||
import Menus from '@src/components/Menus';
|
||||
import Table from '@src/components/Table';
|
||||
import Message from '@src/components/Message';
|
||||
import Search from '@src/components/Search';
|
||||
import SortBy from '@src/components/SortBy';
|
||||
import OrderBy from '@src/components/OrderBy';
|
||||
import RoomRow from './RoomRow';
|
||||
|
||||
// Types
|
||||
import { IRoom } from '@src/types/room';
|
||||
import { ColumnProps } from '@src/types/common';
|
||||
|
||||
// Constants
|
||||
import { ORDERBY_OPTIONS, ROOM_PAGE } from '@src/constants/commons';
|
||||
@@ -22,20 +19,44 @@ import Spinner from '@src/commons/styles/Spinner';
|
||||
|
||||
// Hooks
|
||||
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 columnName = ['Id', 'Name', 'Price', 'Status'];
|
||||
const {
|
||||
isLoading,
|
||||
rooms,
|
||||
count
|
||||
} = useRooms();
|
||||
const columns: ColumnProps[] = [
|
||||
{
|
||||
key: 'id',
|
||||
title: 'Id',
|
||||
width: 10,
|
||||
},
|
||||
{
|
||||
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(
|
||||
(room: IRoom) => <RoomRow room={room} key={room.id} />,
|
||||
[]
|
||||
);
|
||||
const tempRooms = rooms?.map((room) => ({
|
||||
...room,
|
||||
status: room.status
|
||||
? 'Unavailable'
|
||||
: 'Available',
|
||||
onClick: () => console.log(room),
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -49,16 +70,8 @@ const RoomTable = () => {
|
||||
|
||||
{isLoading && <Spinner />}
|
||||
|
||||
{rooms && rooms.length ? (
|
||||
<Menus>
|
||||
<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>
|
||||
{tempRooms && tempRooms.length ? (
|
||||
<Table<IRoomTable> columns={columns} rows={tempRooms} count={count}/>
|
||||
) : (
|
||||
!isLoading && <Message>No data to show here!</Message>
|
||||
)}
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
// Components
|
||||
import Menus from '@src/components/Menus';
|
||||
import Table from '@src/components/Table';
|
||||
import Message from '@src/components/Message';
|
||||
import Search from '@src/components/Search';
|
||||
import SortBy from '@src/components/SortBy';
|
||||
import OrderBy from '@src/components/OrderBy';
|
||||
import UserRow from './UserRow';
|
||||
import Pagination from '@src/components/Pagination';
|
||||
|
||||
// Types
|
||||
import { IUser } from '@src/types/user';
|
||||
|
||||
// Constants
|
||||
import { ORDERBY_OPTIONS, USER_PAGE } from '@src/constants/commons';
|
||||
@@ -23,29 +15,46 @@ import Spinner from '@src/commons/styles/Spinner';
|
||||
|
||||
// Hooks
|
||||
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 columnName = [
|
||||
'Id',
|
||||
'Name',
|
||||
'Phone',
|
||||
'Is Booked'
|
||||
const columns: ColumnProps[] = [
|
||||
{
|
||||
key: 'id',
|
||||
title: 'Id',
|
||||
width: 10,
|
||||
},
|
||||
{
|
||||
key: 'name',
|
||||
title: 'Name',
|
||||
width: 35,
|
||||
},
|
||||
{
|
||||
key: 'phone',
|
||||
title: 'Phone',
|
||||
width: 30,
|
||||
},
|
||||
{
|
||||
key: 'isBooked',
|
||||
title: 'Is Booked',
|
||||
width: 20,
|
||||
},
|
||||
];
|
||||
const {
|
||||
isLoading,
|
||||
users,
|
||||
count
|
||||
} = useUsers();
|
||||
const { isLoading, users, count } = useUsers();
|
||||
|
||||
const renderUserRow = useCallback(
|
||||
(user: IUser) => (
|
||||
<UserRow
|
||||
user={user}
|
||||
key={user.id}
|
||||
/>
|
||||
),
|
||||
[]
|
||||
);
|
||||
const tempUsers = users?.map((user) => ({
|
||||
...user,
|
||||
isBooked: user.isBooked
|
||||
? 'Yes'
|
||||
: 'No',
|
||||
onClick: () => console.log(user),
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -59,16 +68,8 @@ const UserTable = () => {
|
||||
|
||||
{isLoading && <Spinner />}
|
||||
|
||||
{users && users.length ? (
|
||||
<Menus>
|
||||
<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>
|
||||
{tempUsers && tempUsers.length ? (
|
||||
<Table<IUserTable> columns={columns} rows={tempUsers} count={count}/>
|
||||
) : (
|
||||
!isLoading && <Message>No data to show here!</Message>
|
||||
)}
|
||||
|
||||
@@ -12,6 +12,18 @@ interface ILogin {
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface ColumnProps {
|
||||
key: string;
|
||||
title: string;
|
||||
width: number;
|
||||
}
|
||||
|
||||
type TDirection = 'vertical' | 'horizontal';
|
||||
|
||||
export type { Nullable, IDataState, TDirection, ILogin };
|
||||
export type {
|
||||
Nullable,
|
||||
IDataState,
|
||||
TDirection,
|
||||
ILogin,
|
||||
ColumnProps
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user