diff --git a/hotel-management/src/components/Empty.tsx b/hotel-management/src/components/Empty.tsx new file mode 100644 index 0000000..2c89f4e --- /dev/null +++ b/hotel-management/src/components/Empty.tsx @@ -0,0 +1,13 @@ +import styled from 'styled-components'; + +const StyledMessage = styled.p` + font-size: var(--fs-sm); + text-align: center; + padding: 20px; +`; + +const Empty = ({ children }: { children: string }) => { + return {children}; +}; + +export default Empty; diff --git a/hotel-management/src/components/Menus/Menus.tsx b/hotel-management/src/components/Menus/Menus.tsx new file mode 100644 index 0000000..04d726f --- /dev/null +++ b/hotel-management/src/components/Menus/Menus.tsx @@ -0,0 +1,80 @@ +import { useContext, useState } from 'react'; + +// Components +import { HiEllipsisVertical } from 'react-icons/hi2'; +import { useOutsideClick } from '../../hooks/useOutsideClick'; +import { StyledButton, StyledList, StyledMenu, StyledToggle } from './styled'; + +// Interfaces +import { IButton } from '../../globals/interfaces'; + +// Contexts +import MenusContext from '../../contexts/MenuContext'; + +const Menus = ({ children }: { children: JSX.Element }) => { + const [openId, setOpenId] = useState(''); + + const close = () => setOpenId(''); + const open = setOpenId; + + return ( + + {children} + + ); +}; + +const Toggle = ({ id }: { id: string }): React.JSX.Element => { + const { openId, close, open } = useContext(MenusContext); + + const handleClick = (e: React.MouseEvent) => { + e.stopPropagation(); + + openId === '' || openId !== id ? open!(id) : close!(); + }; + + return ( + handleClick(e)}> + + + ); +}; + +const List = ({ + id, + children, +}: { + id: string; + children: JSX.Element[]; +}): React.JSX.Element | null => { + const { openId, close } = useContext(MenusContext); + const ref = useOutsideClick(close!, false); + + if (openId !== id) return null; + + return {children}; +}; + +const Button = ({ children, icon, onClick }: IButton): React.JSX.Element => { + const { close } = useContext(MenusContext); + + const handleClick = () => { + onClick?.(); + close!(); + }; + + return ( +
  • + + {icon} {children} + +
  • + ); +}; + +Menus.Menu = StyledMenu; +Menus.Toggle = Toggle; +Menus.List = List; +Menus.Button = Button; + +export default Menus; diff --git a/hotel-management/src/components/Menus/styled.ts b/hotel-management/src/components/Menus/styled.ts new file mode 100644 index 0000000..33079a7 --- /dev/null +++ b/hotel-management/src/components/Menus/styled.ts @@ -0,0 +1,63 @@ +import styled from 'styled-components'; + +export const StyledMenu = styled.div` + position: relative; + display: flex; + align-items: center; + justify-content: flex-end; +`; + +export const StyledButton = styled.button` + width: 100%; + text-align: left; + background: none; + border: none; + padding: 10px 20px; + font-size: var(--fs-sm-x); + transition: all 0.2s; + + display: flex; + align-items: center; + gap: 10px; + + &:hover { + background-color: var(--hover-background-color); + } + + & svg { + width: 18px; + height: 18px; + color: var(--primary-color); + transition: all 0.3s; + } +`; + +export const StyledToggle = styled.button` + background: none; + border: none; + padding: 8px; + border-radius: var(--radius-sm); + transform: translateX(5px); + transition: all 0.2s; + + &:hover { + background-color: var(--hover-background-color); + } + + & svg { + width: 18px; + height: 18px; + } +`; + +export const StyledList = styled.ul` + position: absolute; + z-index: 1000; + + background-color: white; + box-shadow: var(--shadow-sm); + border-radius: var(--radius-sm); + + right: -10px; + top: 40px; +`; diff --git a/hotel-management/src/components/Table/index.tsx b/hotel-management/src/components/Table/index.tsx new file mode 100644 index 0000000..8f48a46 --- /dev/null +++ b/hotel-management/src/components/Table/index.tsx @@ -0,0 +1,41 @@ +import { ReactNode, useContext } from 'react'; + +// Components +import { StyledBody, StyledHeader, StyledRow, StyledTable } from './styled'; + +// Interfaces +import { ITable, ITableBody } from '../../globals/interfaces'; + +// Contexts +import TableContext from '../../contexts/TableContext'; + +type CallbackMapFunc = (value: T, index: number, array: T[]) => ReactNode; + +const Table = ({ columns, children }: ITable) => { + return ( + + {children} + + ); +}; + +const Header = ({ children }: ITable) => { + const columns = useContext(TableContext); + return {children}; +}; + +const Body = ({ data, render }: ITableBody) => { + if (data && data.length > 0) + return {data?.map(render as CallbackMapFunc)}; +}; + +const Row = ({ children }: ITable) => { + const columns = useContext(TableContext); + return {children}; +}; + +Table.Header = Header; +Table.Body = Body; +Table.Row = Row; + +export default Table; diff --git a/hotel-management/src/components/Table/styled.ts b/hotel-management/src/components/Table/styled.ts new file mode 100644 index 0000000..4906f3f --- /dev/null +++ b/hotel-management/src/components/Table/styled.ts @@ -0,0 +1,43 @@ +import styled from 'styled-components'; + +// Interfaces +import { ITable } from '../../globals/interfaces'; + +export const StyledTable = styled.div` + border: 1px solid var(--border-color); + + font-size: 20px; + border-radius: var(--radius-md); +`; + +const CommonRow = styled.div` + display: grid; + grid-template-columns: ${(props) => props.columns}; + column-gap: 10px; + align-items: center; +`; + +export const StyledBody = styled.div` + font-size: var(--fs-sm-x); +`; + +export const StyledRow = styled(CommonRow)` + padding: 20px; + + &:not(:last-child) { + border-bottom: 1px solid var(--border-color); + } +`; + +export const StyledHeader = styled(CommonRow)` + padding: 10px 20px; + + border-bottom: 1px solid var(--border-color); + background-color: var(--header-table-color); + border-top-left-radius: var(--radius-md); + border-top-right-radius: var(--radius-md); + + font-size: var(--fs-sm); + text-transform: capitalize; + font-weight: 600; +`; diff --git a/hotel-management/src/contexts/MenuContext.ts b/hotel-management/src/contexts/MenuContext.ts new file mode 100644 index 0000000..2f051cc --- /dev/null +++ b/hotel-management/src/contexts/MenuContext.ts @@ -0,0 +1,8 @@ +import { createContext } from 'react'; + +// Interfaces +import { IMenusContext } from '../globals/interfaces'; + +const MenusContext = createContext({}); + +export default MenusContext; diff --git a/hotel-management/src/contexts/TableContext.ts b/hotel-management/src/contexts/TableContext.ts new file mode 100644 index 0000000..394f4a7 --- /dev/null +++ b/hotel-management/src/contexts/TableContext.ts @@ -0,0 +1,5 @@ +import { createContext } from 'react'; + +const TableContext = createContext(''); + +export default TableContext; diff --git a/hotel-management/src/globals/interfaces.ts b/hotel-management/src/globals/interfaces.ts new file mode 100644 index 0000000..6dbad0e --- /dev/null +++ b/hotel-management/src/globals/interfaces.ts @@ -0,0 +1,21 @@ +export interface IMenusContext { + openId?: string; + close?: () => void; + open?: React.Dispatch>; +} + +export interface IButton { + children?: string; + icon?: JSX.Element; + onClick?: () => void; +} + +export interface ITable { + columns?: string; + children: React.ReactNode; +} + +export interface ITableBody { + data?: T[]; + render?: (value: T) => JSX.Element; +} diff --git a/hotel-management/src/globals/types.ts b/hotel-management/src/globals/types.ts new file mode 100644 index 0000000..3f870af --- /dev/null +++ b/hotel-management/src/globals/types.ts @@ -0,0 +1,8 @@ +export type TUser = { + id: string; + name: string; + identifiedCode: string; + address: string; + phone: string; + roomId: string; +}; diff --git a/hotel-management/src/hooks/useOutsideClick.ts b/hotel-management/src/hooks/useOutsideClick.ts new file mode 100644 index 0000000..c7278d5 --- /dev/null +++ b/hotel-management/src/hooks/useOutsideClick.ts @@ -0,0 +1,23 @@ +import { useEffect, useRef } from 'react'; + +export const useOutsideClick = ( + handler: () => void, + listeningCapturing = true, +): React.MutableRefObject => { + const ref = useRef(null); + + useEffect(() => { + const handleClick = (e: Event) => { + if (ref.current && !ref.current.contains(e.target as Node)) { + handler(); + } + }; + + document.addEventListener('click', handleClick, listeningCapturing); + + return () => + document.removeEventListener('click', handleClick, listeningCapturing); + }, [handler, listeningCapturing]); + + return ref; +};