reponsive and selective actions and row actioin + pagination
This commit is contained in:
+27
-27
@@ -1,40 +1,40 @@
|
|||||||
import { ButtonHTMLAttributes, FC, memo, ReactNode } from 'react'
|
import { FC, ReactNode } from 'react'
|
||||||
import MoonLoader from "react-spinners/MoonLoader"
|
|
||||||
import { XOR } from '../helpers/types'
|
|
||||||
import { clx } from '../helpers/utils'
|
import { clx } from '../helpers/utils'
|
||||||
|
|
||||||
type Props = {
|
interface ButtonProps {
|
||||||
|
label?: string;
|
||||||
|
children?: ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
isLoading?: boolean,
|
loading?: boolean;
|
||||||
percentage?: number,
|
onClick?: () => void;
|
||||||
} & ButtonHTMLAttributes<HTMLButtonElement> &
|
type?: 'button' | 'submit' | 'reset';
|
||||||
XOR<{ children: ReactNode }, { label: string }>;
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
const Button: FC<Props> = memo((props: Props) => {
|
const Button: FC<ButtonProps> = (props) => {
|
||||||
|
|
||||||
const buttonClass = clx(
|
const buttonClass = clx(
|
||||||
'flex rounded-xl items-center justify-center text-center h-10 text-sm bg-primary text-white w-full',
|
'w-full bg-primary text-white rounded-xl font-normal text-xs md:text-sm h-10 px-3 md:px-4 transition-all duration-200 hover:bg-opacity-90 disabled:opacity-50 disabled:cursor-not-allowed',
|
||||||
props.disabled && 'cursor-not-allowed opacity-60',
|
|
||||||
props.className
|
props.className
|
||||||
);
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button {...props} className={`${buttonClass} ${props.className}`} >
|
<button
|
||||||
{
|
onClick={props.onClick}
|
||||||
props.isLoading ?
|
type={props.type || 'button'}
|
||||||
<div className='flex gap-2 items-center'>
|
disabled={props.disabled || props.loading}
|
||||||
{
|
className={buttonClass}
|
||||||
!props.percentage ?
|
>
|
||||||
<MoonLoader size={20} color='#fff' />
|
{props.loading ? (
|
||||||
:
|
<div className="flex items-center justify-center gap-2">
|
||||||
<span>{props.percentage}%</span>
|
<div className="w-3 h-3 md:w-4 md:h-4 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
|
||||||
}
|
درحال بارگذاری...
|
||||||
</div>
|
</div>
|
||||||
:
|
) : (
|
||||||
props.label || props.children
|
props.children || props.label
|
||||||
}
|
)}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
|
||||||
export default Button
|
export default Button
|
||||||
@@ -12,7 +12,7 @@ const DefaultTableSkeleton: FC<Props> = ({ tdCount, trCount = 5 }) => {
|
|||||||
<Fragment>
|
<Fragment>
|
||||||
{
|
{
|
||||||
Array.from({ length: trCount }).map((_, rowIndex) => (
|
Array.from({ length: trCount }).map((_, rowIndex) => (
|
||||||
<tr className="tr" key={rowIndex}>
|
<tr className="w-full h-[64px] md:h-[74px] bg-white border-t border-[#EAEDF5]" key={rowIndex}>
|
||||||
{
|
{
|
||||||
Array.from({ length: tdCount }).map((_, colIndex) => (
|
Array.from({ length: tdCount }).map((_, colIndex) => (
|
||||||
<Td text={''} key={colIndex}>
|
<Td text={''} key={colIndex}>
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ const Filters: FC<FiltersProps> = ({
|
|||||||
fields,
|
fields,
|
||||||
onChange,
|
onChange,
|
||||||
initialValues = {},
|
initialValues = {},
|
||||||
className = "mt-10 flex justify-between items-center",
|
className = "mt-6 md:mt-8 xl:mt-10 flex flex-col md:flex-row justify-between items-start md:items-center gap-4",
|
||||||
fieldClassName = "flex gap-4",
|
fieldClassName = "flex flex-col sm:flex-row gap-3 md:gap-4 w-full md:w-auto",
|
||||||
searchField = "search" // پیشفرض فیلد سرچ با نام search
|
searchField = "search" // پیشفرض فیلد سرچ با نام search
|
||||||
}) => {
|
}) => {
|
||||||
const [filters, setFilters] = useState<FilterValues>({});
|
const [filters, setFilters] = useState<FilterValues>({});
|
||||||
@@ -111,7 +111,7 @@ const Filters: FC<FiltersProps> = ({
|
|||||||
key={field.name}
|
key={field.name}
|
||||||
placeholder={field.placeholder}
|
placeholder={field.placeholder}
|
||||||
variant="search"
|
variant="search"
|
||||||
className="max-w-[200px]"
|
className="w-full md:max-w-[200px]"
|
||||||
value={currentValue || field.defaultValue || ''}
|
value={currentValue || field.defaultValue || ''}
|
||||||
onChange={(e) => handleInputChange(field.name, e)}
|
onChange={(e) => handleInputChange(field.name, e)}
|
||||||
/>
|
/>
|
||||||
@@ -129,7 +129,7 @@ const Filters: FC<FiltersProps> = ({
|
|||||||
{otherFields.map(renderField)}
|
{otherFields.map(renderField)}
|
||||||
</div>
|
</div>
|
||||||
{searchFieldObj && (
|
{searchFieldObj && (
|
||||||
<div>
|
<div className="w-full md:w-auto">
|
||||||
{renderField(searchFieldObj)}
|
{renderField(searchFieldObj)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -96,7 +96,6 @@ const Input: FC<Props> = (props: Props) => {
|
|||||||
{
|
{
|
||||||
props.type === 'password' &&
|
props.type === 'password' &&
|
||||||
<Eye onClick={() => setShowPassword((oldValue) => !oldValue)} size={20} color='#8C90A3' className='absolute top-0 bottom-0 cursor-pointer my-auto left-3' />
|
<Eye onClick={() => setShowPassword((oldValue) => !oldValue)} size={20} color='#8C90A3' className='absolute top-0 bottom-0 cursor-pointer my-auto left-3' />
|
||||||
// <img onClick={() => setShowPassword((oldValue) => !oldValue)} src={EyeIcon} className='w-5 absolute top-0 bottom-0 cursor-pointer my-auto left-3' />
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface PaginationProps {
|
||||||
|
currentPage: number;
|
||||||
|
totalPages: number;
|
||||||
|
onPageChange: (page: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Pagination: React.FC<PaginationProps> = ({
|
||||||
|
currentPage,
|
||||||
|
totalPages,
|
||||||
|
onPageChange,
|
||||||
|
}) => {
|
||||||
|
// اگر فقط یک صفحه داریم، pagination نمایش داده نمیشود
|
||||||
|
if (totalPages <= 1) return null;
|
||||||
|
|
||||||
|
const getPageNumbers = () => {
|
||||||
|
const pageNumbers: (number | string)[] = [];
|
||||||
|
const maxVisiblePages = window.innerWidth < 768 ? 3 : 5; // تعداد کمتر برای موبایل
|
||||||
|
|
||||||
|
if (totalPages <= maxVisiblePages) {
|
||||||
|
for (let i = 1; i <= totalPages; i++) {
|
||||||
|
pageNumbers.push(i);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// نمایش صفحه اول
|
||||||
|
pageNumbers.push(1);
|
||||||
|
|
||||||
|
if (currentPage > 3) {
|
||||||
|
pageNumbers.push("...");
|
||||||
|
}
|
||||||
|
|
||||||
|
// صفحات میانی
|
||||||
|
const start = Math.max(2, Math.min(currentPage - 1, totalPages - 3));
|
||||||
|
const end = Math.min(totalPages - 1, Math.max(currentPage + 1, 4));
|
||||||
|
|
||||||
|
for (let i = start; i <= end; i++) {
|
||||||
|
if (!pageNumbers.includes(i)) {
|
||||||
|
pageNumbers.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPage < totalPages - 2) {
|
||||||
|
pageNumbers.push("...");
|
||||||
|
}
|
||||||
|
|
||||||
|
// نمایش صفحه آخر
|
||||||
|
if (!pageNumbers.includes(totalPages)) {
|
||||||
|
pageNumbers.push(totalPages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pageNumbers;
|
||||||
|
};
|
||||||
|
|
||||||
|
const pageNumbers = getPageNumbers();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center items-center gap-1 md:gap-2 py-4 md:py-6 mt-3 md:mt-4 border-t border-gray-100">
|
||||||
|
{/* دکمه صفحه قبل */}
|
||||||
|
<button
|
||||||
|
className={`px-2 md:px-3 py-2 text-xs md:text-sm rounded-lg transition-colors ${currentPage === 1
|
||||||
|
? "text-gray-400 cursor-not-allowed"
|
||||||
|
: "text-gray-600 hover:bg-gray-100 hover:text-gray-800"
|
||||||
|
}`}
|
||||||
|
onClick={() => currentPage > 1 && onPageChange(currentPage - 1)}
|
||||||
|
disabled={currentPage === 1}
|
||||||
|
>
|
||||||
|
قبلی
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* شماره صفحات */}
|
||||||
|
<div className="flex gap-1">
|
||||||
|
{pageNumbers.map((page, index) =>
|
||||||
|
typeof page === "number" ? (
|
||||||
|
<button
|
||||||
|
key={index}
|
||||||
|
className={`w-8 h-8 md:w-10 md:h-10 text-xs md:text-sm rounded-lg transition-colors ${currentPage === page
|
||||||
|
? "bg-primary text-white shadow-sm"
|
||||||
|
: "text-gray-600 hover:bg-gray-100 hover:text-gray-800"
|
||||||
|
}`}
|
||||||
|
onClick={() => onPageChange(page)}
|
||||||
|
>
|
||||||
|
{page}
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<span key={index} className="flex items-center px-1 md:px-2 text-gray-400 text-xs md:text-sm">
|
||||||
|
{page}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* دکمه صفحه بعد */}
|
||||||
|
<button
|
||||||
|
className={`px-2 md:px-3 py-2 text-xs md:text-sm rounded-lg transition-colors ${currentPage === totalPages
|
||||||
|
? "text-gray-400 cursor-not-allowed"
|
||||||
|
: "text-gray-600 hover:bg-gray-100 hover:text-gray-800"
|
||||||
|
}`}
|
||||||
|
onClick={() => currentPage < totalPages && onPageChange(currentPage + 1)}
|
||||||
|
disabled={currentPage === totalPages}
|
||||||
|
>
|
||||||
|
بعدی
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Pagination;
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
|
import { More } from 'iconsax-react';
|
||||||
|
|
||||||
|
export interface RowActionItem {
|
||||||
|
label: string;
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
onClick: () => void;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RowActionsDropdownProps {
|
||||||
|
actions: RowActionItem[];
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RowActionsDropdown: React.FC<RowActionsDropdownProps> = ({ actions, className = '' }) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [position, setPosition] = useState({ top: 0, left: 0 });
|
||||||
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||||
|
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleToggle = (e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
if (!isOpen && buttonRef.current) {
|
||||||
|
const rect = buttonRef.current.getBoundingClientRect();
|
||||||
|
const isMobile = window.innerWidth < 768;
|
||||||
|
const dropdownWidth = isMobile ? 140 : 150;
|
||||||
|
const dropdownHeight = actions.length * (isMobile ? 36 : 40); // تخمین ارتفاع منو
|
||||||
|
|
||||||
|
let left = rect.left + window.scrollX - dropdownWidth + rect.width;
|
||||||
|
let top = rect.bottom + window.scrollY;
|
||||||
|
|
||||||
|
// بررسی اینکه منو از سمت راست خارج نشود
|
||||||
|
if (left + dropdownWidth > window.innerWidth) {
|
||||||
|
left = rect.left + window.scrollX - dropdownWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
// بررسی اینکه منو از سمت چپ خارج نشود
|
||||||
|
if (left < 0) {
|
||||||
|
left = isMobile ? 20 : 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
// بررسی اینکه منو از پایین خارج نشود
|
||||||
|
if (top + dropdownHeight > window.innerHeight + window.scrollY) {
|
||||||
|
top = rect.top + window.scrollY - dropdownHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPosition({ top, left });
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsOpen(!isOpen);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleActionClick = (action: RowActionItem) => {
|
||||||
|
action.onClick();
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderDropdown = () => {
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
return createPortal(
|
||||||
|
<div
|
||||||
|
ref={dropdownRef}
|
||||||
|
className="fixed py-1 md:py-2 bg-white rounded-xl md:rounded-2xl shadow-lg z-[9999] min-w-[140px] md:min-w-[150px]"
|
||||||
|
style={{
|
||||||
|
top: `${position.top}px`,
|
||||||
|
left: `${position.left}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{actions.map((action, index) => (
|
||||||
|
<button
|
||||||
|
key={index}
|
||||||
|
onClick={() => handleActionClick(action)}
|
||||||
|
className={`w-full text-right px-2 md:px-3 py-1 md:py-1.5 text-xs md:text-[13px] flex items-center gap-2 hover:bg-gray-50 ${index === 0 ? 'rounded-t-lg' : ''
|
||||||
|
} ${action.className || ''}`}
|
||||||
|
>
|
||||||
|
{action.icon && <span className="ml-2">{action.icon}</span>}
|
||||||
|
{action.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>,
|
||||||
|
document.body
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={className}>
|
||||||
|
<button
|
||||||
|
ref={buttonRef}
|
||||||
|
onClick={handleToggle}
|
||||||
|
className="p-1 hover:bg-gray-100 rounded-full transition-colors"
|
||||||
|
>
|
||||||
|
<More size={14} color="black" className="rotate-90" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{renderDropdown()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RowActionsDropdown;
|
||||||
+119
-50
@@ -3,6 +3,8 @@ import DefaultTableSkeleton from './DefaultTableSkeleton';
|
|||||||
import Td from './Td';
|
import Td from './Td';
|
||||||
import { TableProps, RowDataType, ColumnType } from './types/TableTypes';
|
import { TableProps, RowDataType, ColumnType } from './types/TableTypes';
|
||||||
import { Checkbox } from './ui/checkbox';
|
import { Checkbox } from './ui/checkbox';
|
||||||
|
import RowActionsDropdown from './RowActionsDropdown';
|
||||||
|
import Pagination from './Pagination';
|
||||||
|
|
||||||
const Table = <T extends RowDataType>({
|
const Table = <T extends RowDataType>({
|
||||||
columns,
|
columns,
|
||||||
@@ -18,6 +20,10 @@ const Table = <T extends RowDataType>({
|
|||||||
actions = null,
|
actions = null,
|
||||||
actionsPosition = 'top',
|
actionsPosition = 'top',
|
||||||
selectable = false,
|
selectable = false,
|
||||||
|
selectedActions = null,
|
||||||
|
onSelectionChange,
|
||||||
|
rowActions,
|
||||||
|
pagination,
|
||||||
}: TableProps<T>): React.ReactElement => {
|
}: TableProps<T>): React.ReactElement => {
|
||||||
|
|
||||||
const [selectedRows, setSelectedRows] = useState<T[]>([]);
|
const [selectedRows, setSelectedRows] = useState<T[]>([]);
|
||||||
@@ -31,36 +37,42 @@ const Table = <T extends RowDataType>({
|
|||||||
|
|
||||||
const getCellClassName = (column: ColumnType<T>): string => {
|
const getCellClassName = (column: ColumnType<T>): string => {
|
||||||
const alignClass = column.align ? `text-${column.align}` : '';
|
const alignClass = column.align ? `text-${column.align}` : '';
|
||||||
return `p-3 ${alignClass} ${column.className || ''}`.trim();
|
return `px-3 md:px-6 py-3 md:py-4 whitespace-nowrap text-xs ${alignClass} ${column.className || ''}`.trim();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRowClick = (item: T) => {
|
const handleRowClick = (item: T) => {
|
||||||
if (selectable) {
|
|
||||||
if (selectedRows.includes(item)) {
|
|
||||||
setSelectedRows(selectedRows.filter((row) => row !== item));
|
|
||||||
} else {
|
|
||||||
setSelectedRows([...selectedRows, item]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onRowClick) {
|
if (onRowClick) {
|
||||||
onRowClick(item.id, item);
|
onRowClick(item.id, item);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelectAll = () => {
|
const handleSelectAll = () => {
|
||||||
if (selectedRows.length === data.length) {
|
const newSelectedRows = selectedRows.length === data.length ? [] : [...data];
|
||||||
setSelectedRows([]);
|
setSelectedRows(newSelectedRows);
|
||||||
} else {
|
if (onSelectionChange) {
|
||||||
setSelectedRows([...data]);
|
onSelectionChange(newSelectedRows);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSingleRowSelect = (item: T, checked: boolean) => {
|
||||||
|
const newSelectedRows = checked
|
||||||
|
? [...selectedRows, item]
|
||||||
|
: selectedRows.filter((row) => row.id !== item.id);
|
||||||
|
setSelectedRows(newSelectedRows);
|
||||||
|
if (onSelectionChange) {
|
||||||
|
onSelectionChange(newSelectedRows);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isRowSelected = (item: T): boolean => {
|
||||||
|
return selectedRows.some((row) => row.id === item.id);
|
||||||
|
};
|
||||||
|
|
||||||
const renderTableBody = () => {
|
const renderTableBody = () => {
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<DefaultTableSkeleton tdCount={columns.length} trCount={emptyRowsCount} />
|
<DefaultTableSkeleton tdCount={columns.length + (selectable ? 1 : 0) + (rowActions ? 1 : 0)} trCount={emptyRowsCount} />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -68,7 +80,7 @@ const Table = <T extends RowDataType>({
|
|||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
return (
|
return (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={columns.length} className="p-8 text-center text-gray-500">
|
<td colSpan={columns.length + (selectable ? 1 : 0) + (rowActions ? 1 : 0)} className="px-3 md:px-6 py-6 md:py-8 text-center text-gray-500">
|
||||||
{noDataMessage}
|
{noDataMessage}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -77,28 +89,31 @@ const Table = <T extends RowDataType>({
|
|||||||
return data.map((item, rowIndex) => (
|
return data.map((item, rowIndex) => (
|
||||||
<tr
|
<tr
|
||||||
key={item.id}
|
key={item.id}
|
||||||
className={`tr border-b hover:bg-gray-50 ${onRowClick ? 'cursor-pointer' : ''} ${getRowClassName(item, rowIndex)}`}
|
className={`w-full h-[64px] md:h-[74px] bg-white border-t border-[#EAEDF5] hover:bg-[#F4F5F9] ${onRowClick ? 'cursor-pointer' : ''} ${getRowClassName(item, rowIndex)}`}
|
||||||
onClick={() => handleRowClick(item)}
|
onClick={() => handleRowClick(item)}
|
||||||
>
|
>
|
||||||
{selectable && (
|
{selectable && (
|
||||||
<td className="p-3 w-10 relative z-[5]">
|
<td className="px-3 md:px-6 py-3 md:py-4 w-8 md:w-10 relative z-[5]">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={selectedRows.includes(item)}
|
checked={isRowSelected(item)}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => handleSingleRowSelect(item, !!checked)}
|
||||||
if (checked) {
|
|
||||||
setSelectedRows([...selectedRows, item]);
|
|
||||||
} else {
|
|
||||||
setSelectedRows(selectedRows.filter((row) => row !== item));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
)}
|
)}
|
||||||
{columns.map((col) => (
|
{columns.map((col) => (
|
||||||
<td key={`${item.id}-${col.key}`} className={getCellClassName(col)} style={{ width: col.width }}>
|
<td
|
||||||
|
key={`${item.id}-${col.key}`}
|
||||||
|
className={getCellClassName(col)}
|
||||||
|
style={{ width: col.width }}
|
||||||
|
>
|
||||||
{col.render ? col.render(item) : item[col.key] as React.ReactNode}
|
{col.render ? col.render(item) : item[col.key] as React.ReactNode}
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
|
{rowActions && (
|
||||||
|
<td className="px-3 md:px-6 py-3 md:py-4 w-8 md:w-10 relative z-[5]">
|
||||||
|
<RowActionsDropdown actions={rowActions(item)} />
|
||||||
|
</td>
|
||||||
|
)}
|
||||||
</tr>
|
</tr>
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
@@ -107,12 +122,10 @@ const Table = <T extends RowDataType>({
|
|||||||
if (!showHeader || actionsPosition === 'header-replace') return null;
|
if (!showHeader || actionsPosition === 'header-replace') return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<thead className={`thead ${headerClassName}`}>
|
<thead className={`h-[60px] md:h-[69px] bg-white text-sm text-[#8C90A3] ${headerClassName}`}>
|
||||||
<tr>
|
<tr>
|
||||||
{selectable && (
|
{selectable && (
|
||||||
<th
|
<th className="px-3 md:px-6 py-3 md:py-4 w-8 md:w-10 relative z-[5]">
|
||||||
className="p-3 w-10 relative z-[5]"
|
|
||||||
>
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={data.length > 0 && selectedRows.length === data.length}
|
checked={data.length > 0 && selectedRows.length === data.length}
|
||||||
onCheckedChange={handleSelectAll}
|
onCheckedChange={handleSelectAll}
|
||||||
@@ -128,41 +141,97 @@ const Table = <T extends RowDataType>({
|
|||||||
<Td text={col.title} />
|
<Td text={col.title} />
|
||||||
</th>
|
</th>
|
||||||
))}
|
))}
|
||||||
|
{rowActions && (
|
||||||
|
<th className="px-3 md:px-6 py-3 md:py-4 w-8 md:w-10"></th>
|
||||||
|
)}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderActions = () => {
|
const renderActions = () => {
|
||||||
if (!actions) return null;
|
if (!actions && (!selectable || selectedRows.length === 0 || !selectedActions)) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'action'}>
|
<div className="h-[64px] rounded-t-2xl md:rounded-t-3xl md:h-[74px] bg-white flex items-center px-3 md:px-6">
|
||||||
{actions}
|
<div className='flex items-center gap-2 md:gap-4'>
|
||||||
|
{actions}
|
||||||
|
{selectable && selectedRows.length > 0 && selectedActions && selectedActions}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`relative overflow-x-auto rounded-3xl mt-9 w-full ${className}`}>
|
<div className={`relative mt-6 md:mt-9 w-full ${className}`}>
|
||||||
{(actionsPosition === 'top' || actionsPosition === 'above-header') && renderActions()}
|
{(actionsPosition === 'top' || actionsPosition === 'above-header') && renderActions()}
|
||||||
<table className="w-full text-sm border-collapse">
|
|
||||||
{renderHeader()}
|
<div className="overflow-x-auto overflow-hidden rounded-b-2xl md:rounded-b-3xl bg-white shadow-sm">
|
||||||
{actionsPosition === 'header-replace' && (
|
<table className="w-full text-sm border-collapse min-w-[600px]">
|
||||||
<thead>
|
{renderHeader()}
|
||||||
<tr>
|
{actionsPosition === 'header-replace' && (
|
||||||
<th colSpan={columns.length} className="p-0">
|
<thead>
|
||||||
{renderActions()}
|
<tr>
|
||||||
</th>
|
<th colSpan={columns.length} className="p-0">
|
||||||
</tr>
|
{renderActions()}
|
||||||
</thead>
|
</th>
|
||||||
)}
|
</tr>
|
||||||
<tbody>
|
</thead>
|
||||||
{renderTableBody()}
|
)}
|
||||||
</tbody>
|
<tbody>
|
||||||
</table>
|
{renderTableBody()}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{pagination && (
|
||||||
|
<Pagination
|
||||||
|
currentPage={pagination.currentPage}
|
||||||
|
totalPages={pagination.totalPages}
|
||||||
|
onPageChange={pagination.onPageChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Table;
|
export default Table;
|
||||||
|
|
||||||
|
/*
|
||||||
|
مثال استفاده با pagination:
|
||||||
|
|
||||||
|
import Table from '@/components/Table';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
const MyComponent = () => {
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const totalPages = 10;
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ title: 'نام', key: 'name' },
|
||||||
|
{ title: 'ایمیل', key: 'email' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const data = [
|
||||||
|
{ id: 1, name: 'علی', email: 'ali@example.com' },
|
||||||
|
{ id: 2, name: 'فاطمه', email: 'fateme@example.com' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const handlePageChange = (page: number) => {
|
||||||
|
setCurrentPage(page);
|
||||||
|
// اینجا میتوانید API call جدید برای دریافت دادههای صفحه جدید بزنید
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
data={data}
|
||||||
|
pagination={{
|
||||||
|
currentPage,
|
||||||
|
totalPages,
|
||||||
|
onPageChange: handlePageChange
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
*/
|
||||||
@@ -24,19 +24,19 @@ const Tabs: FC<Props> = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<Swiper
|
<Swiper
|
||||||
slidesPerView='auto'
|
slidesPerView='auto'
|
||||||
spaceBetween={30}
|
spaceBetween={20}
|
||||||
className='px-10 max-w-full w-fit items-center text-description mx-auto backdrop-blur-md border-2 border-white gap-10 flex h-[80px] rounded-[32px] bg-white bg-opacity-45'>
|
className='px-4 md:px-8 xl:px-10 max-w-full w-fit items-center text-description mx-auto backdrop-blur-md border-2 border-white gap-4 md:gap-6 xl:gap-10 flex h-[60px] md:h-[70px] xl:h-[80px] rounded-[20px] md:rounded-[28px] xl:rounded-[32px] bg-white bg-opacity-45'>
|
||||||
{
|
{
|
||||||
props.items.map((item: Item, index: number) => {
|
props.items.map((item: Item, index: number) => {
|
||||||
return (
|
return (
|
||||||
<SwiperSlide style={SWIPER_SLIDE_STYLE} onClick={() => props.onChange(item.value)} key={item.value} className={clx(
|
<SwiperSlide style={SWIPER_SLIDE_STYLE} onClick={() => props.onChange(item.value)} key={item.value} className={clx(
|
||||||
'flex flex-col max-w-fit mt-[15px] items-center gap-2 cursor-pointer',
|
'flex flex-col max-w-fit mt-[10px] md:mt-[12px] xl:mt-[15px] items-center gap-1 md:gap-2 cursor-pointer',
|
||||||
index === 0 && 'pr-[30px]',
|
index === 0 && 'pr-[15px] md:pr-[20px] xl:pr-[30px]',
|
||||||
index === props.items.length - 1 && props.items.length > 3 && 'pl-[5px]',
|
index === props.items.length - 1 && props.items.length > 3 && 'pl-[5px]',
|
||||||
props.active === item.value && 'text-black'
|
props.active === item.value && 'text-black'
|
||||||
)}>
|
)}>
|
||||||
{item.icon}
|
{item.icon}
|
||||||
<div className='text-xs'>
|
<div className='text-[10px] md:text-xs'>
|
||||||
{item.label}
|
{item.label}
|
||||||
</div>
|
</div>
|
||||||
</SwiperSlide>
|
</SwiperSlide>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ interface Props {
|
|||||||
|
|
||||||
const Td: FC<Props> = (props: Props) => {
|
const Td: FC<Props> = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<td className='td' style={{ direction: props.dir === "ltr" ? "ltr" : "rtl" }}>
|
<td className='px-3 md:px-6 py-3 md:py-4 whitespace-nowrap text-xs' style={{ direction: props.dir === "ltr" ? "ltr" : "rtl" }}>
|
||||||
{
|
{
|
||||||
props.text ?
|
props.text ?
|
||||||
props.text
|
props.text
|
||||||
|
|||||||
@@ -17,59 +17,60 @@ const NewMessage: FC = () => {
|
|||||||
{openNewMessage && (
|
{openNewMessage && (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className='fixed inset-0 bg-opacity-50 z-[9998]'
|
className='fixed inset-0 bg-black/50 z-[9998]'
|
||||||
onClick={() => setOpenNewMessage(false)}
|
onClick={() => setOpenNewMessage(false)}
|
||||||
// style={{ left: '250px' }}
|
|
||||||
/>
|
/>
|
||||||
<div className={`fixed left-8 bottom-4 bg-white rounded-3xl shadow-[0_0_20px_rgba(0,0,0,0.1)] z-[9999] transition-all duration-300 p-8 w-[800px] `}>
|
<div className='fixed left-2 right-2 bottom-2 md:left-4 md:right-4 md:bottom-4 md:top-4 xl:left-8 xl:right-auto xl:bottom-4 xl:top-4 bg-white rounded-2xl md:rounded-3xl shadow-[0_0_20px_rgba(0,0,0,0.1)] z-[9999] transition-all duration-300 p-4 md:p-6 xl:p-8 w-auto xl:w-[800px] max-h-[90vh] overflow-y-auto'>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className='flex justify-between items-center'>
|
<div className='flex justify-between items-center mb-6 md:mb-8'>
|
||||||
<div className=''>
|
<div className='text-lg md:text-xl xl:text-2xl font-semibold'>
|
||||||
{t('new_message.title')}
|
{t('new_message.title')}
|
||||||
</div>
|
</div>
|
||||||
<div onClick={() => setOpenNewMessage(false)}>
|
<div onClick={() => setOpenNewMessage(false)} className='cursor-pointer p-1'>
|
||||||
<CloseCircle size={24} color='#292D32' />
|
<CloseCircle size={20} className='md:w-6 md:h-6' color='#292D32' />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
|
<div className='space-y-6 md:space-y-8'>
|
||||||
<div className='mt-10'>
|
|
||||||
<Input
|
<Input
|
||||||
label={t('new_message.to')}
|
label={t('new_message.to')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className='mt-8'>
|
<Input
|
||||||
<Input
|
label={t('new_message.subject')}
|
||||||
label={t('new_message.subject')}
|
/>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<ReactQuill
|
||||||
|
modules={{
|
||||||
|
toolbar: [
|
||||||
|
[{ header: '1' }, { header: '2' }, { font: [] }],
|
||||||
|
[{ list: 'ordered' }, { list: 'bullet' }],
|
||||||
|
['bold', 'italic', 'underline'],
|
||||||
|
['link', 'image'],
|
||||||
|
[{ align: [] }],
|
||||||
|
['clean']
|
||||||
|
]
|
||||||
|
}}
|
||||||
|
theme="snow"
|
||||||
|
value={value}
|
||||||
|
onChange={setValue}
|
||||||
|
style={{ minHeight: '120px' }}
|
||||||
|
className='text-sm'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-8'>
|
<div className='flex flex-col sm:flex-row justify-end gap-3 md:gap-4 pt-4'>
|
||||||
<ReactQuill modules={{
|
|
||||||
toolbar: [
|
|
||||||
[{ header: '1' }, { header: '2' }, { font: [] }],
|
|
||||||
[{ list: 'ordered' }, { list: 'bullet' }],
|
|
||||||
['bold', 'italic', 'underline'],
|
|
||||||
['link', 'image'],
|
|
||||||
[{ align: [] }],
|
|
||||||
['clean']
|
|
||||||
]
|
|
||||||
}} theme="snow" value={value} onChange={setValue} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='mt-8 flex justify-end gap-4'>
|
|
||||||
<Button
|
<Button
|
||||||
className='!w-fit px-10 bg-white text-black border border-primary'
|
className='!w-full sm:!w-fit px-6 md:px-10 bg-white text-black border border-primary order-2 sm:order-1'
|
||||||
label={t('new_message.draft')}
|
label={t('new_message.draft')}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
className='w-fit px-10'
|
className='w-full sm:w-fit px-6 md:px-10 order-1 sm:order-2'
|
||||||
label={t('new_message.send')}
|
label={t('new_message.send')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
import { RowActionItem } from "../RowActionsDropdown";
|
||||||
|
|
||||||
export type RowDataType = Record<string, unknown> & { id: string | number };
|
export type RowDataType = Record<string, unknown> & { id: string | number };
|
||||||
|
|
||||||
@@ -12,6 +13,12 @@ export interface ColumnType<T extends RowDataType = RowDataType> {
|
|||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PaginationConfig {
|
||||||
|
currentPage: number;
|
||||||
|
totalPages: number;
|
||||||
|
onPageChange: (page: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
export interface TableProps<T extends RowDataType = RowDataType> {
|
export interface TableProps<T extends RowDataType = RowDataType> {
|
||||||
columns: ColumnType<T>[];
|
columns: ColumnType<T>[];
|
||||||
data: T[];
|
data: T[];
|
||||||
@@ -27,4 +34,8 @@ export interface TableProps<T extends RowDataType = RowDataType> {
|
|||||||
actionsClassName?: string;
|
actionsClassName?: string;
|
||||||
actionsPosition?: "top" | "header-replace" | "above-header";
|
actionsPosition?: "top" | "header-replace" | "above-header";
|
||||||
selectable?: boolean;
|
selectable?: boolean;
|
||||||
|
selectedActions?: ReactNode;
|
||||||
|
onSelectionChange?: (selectedRows: T[]) => void;
|
||||||
|
rowActions?: (item: T) => RowActionItem[];
|
||||||
|
pagination?: PaginationConfig;
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-13
@@ -22,6 +22,17 @@ body {
|
|||||||
font-feature-settings: "lnum"; /* اطمینان از نمایش اعداد انگلیسی */
|
font-feature-settings: "lnum"; /* اطمینان از نمایش اعداد انگلیسی */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ریسپانسیو شدن برای صفحات کوچک */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.no-scrollbar::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.no-scrollbar {
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -52,19 +63,6 @@ textarea::placeholder {
|
|||||||
--radius-2.5: 10px;
|
--radius-2.5: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
td {
|
|
||||||
@apply px-6 py-4 whitespace-nowrap text-xs;
|
|
||||||
}
|
|
||||||
.thead {
|
|
||||||
@apply h-[69px] bg-white text-sm text-[#8C90A3] rounded-3xl overflow-hidden;
|
|
||||||
}
|
|
||||||
.tr {
|
|
||||||
@apply w-full h-[74px] bg-white border-t border-[#EAEDF5] hover:bg-[#F4F5F9];
|
|
||||||
}
|
|
||||||
.action {
|
|
||||||
@apply h-[74px] bg-white flex items-center px-6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rmdp-input {
|
.rmdp-input {
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
|
|||||||
+28
-3
@@ -22,6 +22,8 @@
|
|||||||
"from_date": "از تاریخ",
|
"from_date": "از تاریخ",
|
||||||
"to_date": "تا تاریخ",
|
"to_date": "تا تاریخ",
|
||||||
"all": "همه",
|
"all": "همه",
|
||||||
|
"read": "خوانده شده",
|
||||||
|
"unread": "خوانده نشده",
|
||||||
"search": "جستجو"
|
"search": "جستجو"
|
||||||
},
|
},
|
||||||
"sent": {
|
"sent": {
|
||||||
@@ -29,6 +31,32 @@
|
|||||||
"from_date": "از تاریخ",
|
"from_date": "از تاریخ",
|
||||||
"to_date": "تا تاریخ",
|
"to_date": "تا تاریخ",
|
||||||
"all": "همه",
|
"all": "همه",
|
||||||
|
"read": "خوانده شده",
|
||||||
|
"unread": "خوانده نشده",
|
||||||
|
"search": "جستجو"
|
||||||
|
},
|
||||||
|
"draft": {
|
||||||
|
"title": "پیش نویس ها",
|
||||||
|
"from_date": "از تاریخ",
|
||||||
|
"to_date": "تا تاریخ",
|
||||||
|
"search": "جستجو"
|
||||||
|
},
|
||||||
|
"archive": {
|
||||||
|
"title": "آرشیو شده ها",
|
||||||
|
"from_date": "از تاریخ",
|
||||||
|
"to_date": "تا تاریخ",
|
||||||
|
"all": "همه",
|
||||||
|
"read": "خوانده شده",
|
||||||
|
"unread": "خوانده نشده",
|
||||||
|
"search": "جستجو"
|
||||||
|
},
|
||||||
|
"trash": {
|
||||||
|
"title": "سطل زباله",
|
||||||
|
"from_date": "از تاریخ",
|
||||||
|
"to_date": "تا تاریخ",
|
||||||
|
"all": "همه",
|
||||||
|
"read": "خوانده شده",
|
||||||
|
"unread": "خوانده نشده",
|
||||||
"search": "جستجو"
|
"search": "جستجو"
|
||||||
},
|
},
|
||||||
"setting": {
|
"setting": {
|
||||||
@@ -48,8 +76,5 @@
|
|||||||
"recipients_placeholder": "نام گیرنده",
|
"recipients_placeholder": "نام گیرنده",
|
||||||
"subject_placeholder": "موضوع پیام",
|
"subject_placeholder": "موضوع پیام",
|
||||||
"message_placeholder": "متن پیام"
|
"message_placeholder": "متن پیام"
|
||||||
},
|
|
||||||
"draft": {
|
|
||||||
"title": "پیش نویس ها"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+78
-29
@@ -1,26 +1,28 @@
|
|||||||
import Filters, { FilterValues } from '../../components/Filters';
|
import Filters, { FilterValues } from '../../components/Filters';
|
||||||
import { FC, ReactNode, useState } from 'react'
|
import { FC, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Table from '../../components/Table';
|
import Table from '../../components/Table';
|
||||||
import { ColumnType } from '../../components/types/TableTypes';
|
import { ColumnType } from '../../components/types/TableTypes';
|
||||||
import { InfoCircle, More, Refresh2 } from 'iconsax-react';
|
import { InfoCircle, More, Refresh2, Trash, RecoveryConvert } from 'iconsax-react';
|
||||||
|
import { RowActionItem } from '../../components/RowActionsDropdown';
|
||||||
|
|
||||||
interface Message extends Record<string, unknown> {
|
interface Message extends Record<string, unknown> {
|
||||||
id: string;
|
id: string;
|
||||||
title: string | ReactNode;
|
title: string;
|
||||||
sender: string;
|
sender: string;
|
||||||
date: string;
|
date: string;
|
||||||
read: boolean;
|
read: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TrashList: FC = () => {
|
const List: FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [isLoading] = useState(false);
|
const [isLoading] = useState(false);
|
||||||
|
const [selectedMessages, setSelectedMessages] = useState<Message[]>([]);
|
||||||
|
|
||||||
const messageData: Message[] = [
|
const messageData: Message[] = [
|
||||||
{ id: '1', title: <div className='text-blue-500'>پیش نویس</div>, sender: 'علی محمدی', date: '1402/05/12', read: true },
|
{ id: '1', title: 'اولین پیام', sender: 'علی محمدی', date: '1402/05/12', read: true },
|
||||||
{ id: '2', title: <div className='text-blue-500'>ارسالی</div>, sender: 'زهرا احمدی', date: '1402/05/13', read: true },
|
{ id: '2', title: 'دومین پیام', sender: 'زهرا احمدی', date: '1402/05/13', read: true },
|
||||||
{ id: '3', title: <div className='text-blue-500'>دریافت شده</div>, sender: 'مدیریت', date: '1402/05/14', read: true },
|
{ id: '3', title: 'جلسه هفتگی', sender: 'مدیریت', date: '1402/05/14', read: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
const columns: ColumnType<Message>[] = [
|
const columns: ColumnType<Message>[] = [
|
||||||
@@ -29,49 +31,94 @@ const TrashList: FC = () => {
|
|||||||
title: 'عنوان پیام',
|
title: 'عنوان پیام',
|
||||||
render: (item) => (
|
render: (item) => (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
{!item.read && <div className="w-2 h-2 rounded-full bg-blue-500 mr-2" />}
|
{!item.read && <div className="w-1.5 h-1.5 md:w-2 md:h-2 rounded-full bg-blue-500 mr-2" />}
|
||||||
<span>{item.title}</span>
|
<span className="truncate">{item.title}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{ key: 'status', title: 'وضعیت' },
|
{
|
||||||
{ key: 'sender', title: 'فرستنده' },
|
key: 'sender',
|
||||||
{ key: 'date', title: 'تاریخ', align: 'center' },
|
title: 'فرستنده',
|
||||||
|
render: (item) => (
|
||||||
|
<span className="truncate">{item.sender}</span>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'date',
|
||||||
|
title: 'تاریخ',
|
||||||
|
align: 'center',
|
||||||
|
render: (item) => (
|
||||||
|
<span className="text-xs md:text-sm">{item.date}</span>
|
||||||
|
)
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const tableActions = (
|
const tableActions = (
|
||||||
<div className='flex items-center gap-4'>
|
<div className='flex items-center gap-2 md:gap-4'>
|
||||||
<Refresh2 size={20} color='black' />
|
<Refresh2 size={18} color='black' className="cursor-pointer" />
|
||||||
<More size={20} color='black' className='rotate-90' />
|
<More size={18} color='black' className='rotate-90 cursor-pointer' />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const selectedActions = (
|
||||||
|
<>
|
||||||
|
<RecoveryConvert size={18} color='black' onClick={() => handleRestoreSelected()} className="cursor-pointer" />
|
||||||
|
<Trash size={18} color='red' onClick={() => handleDeleteSelected()} className="cursor-pointer" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
const handleFilterChange = (newFilters: FilterValues) => {
|
const handleFilterChange = (newFilters: FilterValues) => {
|
||||||
console.log('Applied filters:', newFilters);
|
console.log('Applied filters:', newFilters);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSelectionChange = (selectedRows: Message[]) => {
|
||||||
|
setSelectedMessages(selectedRows);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteSelected = () => {
|
||||||
|
console.log('حذف دائم پیامهای انتخاب شده:', selectedMessages.map(m => m.id));
|
||||||
|
// اینجا منطق حذف دائم را پیادهسازی کنید
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRestoreSelected = () => {
|
||||||
|
console.log('بازگردانی پیامهای انتخاب شده:', selectedMessages.map(m => m.id));
|
||||||
|
// اینجا منطق بازگردانی را پیادهسازی کنید
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRowActions = (message: Message): RowActionItem[] => [
|
||||||
|
{
|
||||||
|
label: 'بازگردانی',
|
||||||
|
icon: <RecoveryConvert size={16} color="black" />,
|
||||||
|
onClick: () => console.log('بازگردانی پیام', message.id),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'حذف دائم',
|
||||||
|
icon: <Trash size={16} color="red" />,
|
||||||
|
onClick: () => console.log('حذف دائم پیام', message.id),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-4'>
|
<div className='mt-2 md:mt-4 px-2 md:px-0'>
|
||||||
<h1>{t('draft.title')}</h1>
|
<h1 className="text-lg md:text-xl xl:text-2xl font-bold mb-4 md:mb-0">{t('trash.title')}</h1>
|
||||||
|
|
||||||
<Filters
|
<Filters
|
||||||
fields={[
|
fields={[
|
||||||
{ type: 'date', name: 'from_date', placeholder: t('received.from_date') },
|
{ type: 'date', name: 'from_date', placeholder: t('trash.from_date') },
|
||||||
{ type: 'date', name: 'to_date', placeholder: t('received.to_date') },
|
{ type: 'date', name: 'to_date', placeholder: t('trash.to_date') },
|
||||||
{
|
{
|
||||||
type: 'select',
|
type: 'select',
|
||||||
name: 'status',
|
name: 'status',
|
||||||
placeholder: t('received.all'),
|
placeholder: t('trash.all'),
|
||||||
options: [
|
options: [
|
||||||
{ value: 'all', label: t('received.all') },
|
{ value: 'all', label: t('trash.all') },
|
||||||
{ value: 'read', label: t('received.read') },
|
{ value: 'read', label: t('trash.read') },
|
||||||
{ value: 'unread', label: t('received.unread') }
|
{ value: 'unread', label: t('trash.unread') }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ type: 'input', name: 'search', placeholder: t('received.search') }
|
{ type: 'input', name: 'search', placeholder: t('trash.search') }
|
||||||
]}
|
]}
|
||||||
onChange={handleFilterChange}
|
onChange={handleFilterChange}
|
||||||
className="mt-8 flex justify-between items-center"
|
|
||||||
searchField="search"
|
searchField="search"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -81,18 +128,20 @@ const TrashList: FC = () => {
|
|||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
showHeader={false}
|
showHeader={false}
|
||||||
actions={tableActions}
|
actions={tableActions}
|
||||||
|
selectedActions={selectedActions}
|
||||||
|
onSelectionChange={handleSelectionChange}
|
||||||
onRowClick={(id) => console.log(`کلیک روی پیام با شناسه ${id}`)}
|
onRowClick={(id) => console.log(`کلیک روی پیام با شناسه ${id}`)}
|
||||||
noDataMessage={
|
noDataMessage={
|
||||||
<div className="flex flex-col items-center py-6">
|
<div className="flex flex-col items-center py-6">
|
||||||
<InfoCircle size={40} className="text-gray-300 mb-2" />
|
<InfoCircle size={32} className="text-gray-300 mb-2" />
|
||||||
<div>هیچ پیامی یافت نشد</div>
|
<div className="text-sm">سطل آشغال خالی است</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
selectable={true}
|
selectable={true}
|
||||||
|
rowActions={getRowActions}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TrashList
|
export default List
|
||||||
|
|||||||
+78
-30
@@ -1,27 +1,28 @@
|
|||||||
import Filters, { FilterValues } from '../../components/Filters';
|
import Filters, { FilterValues } from '../../components/Filters';
|
||||||
import { FC, ReactNode, useState } from 'react'
|
import { FC, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Table from '../../components/Table';
|
import Table from '../../components/Table';
|
||||||
import { ColumnType } from '../../components/types/TableTypes';
|
import { ColumnType } from '../../components/types/TableTypes';
|
||||||
import { InfoCircle, More, Refresh2 } from 'iconsax-react';
|
import { InfoCircle, More, Refresh2, Trash, ArchiveSlash } from 'iconsax-react';
|
||||||
|
import { RowActionItem } from '../../components/RowActionsDropdown';
|
||||||
|
|
||||||
interface Message extends Record<string, unknown> {
|
interface Message extends Record<string, unknown> {
|
||||||
id: string;
|
id: string;
|
||||||
title: string | ReactNode;
|
title: string;
|
||||||
status: ReactNode;
|
|
||||||
sender: string;
|
sender: string;
|
||||||
date: string;
|
date: string;
|
||||||
read: boolean;
|
read: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ArchiveList: FC = () => {
|
const List: FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [isLoading] = useState(false);
|
const [isLoading] = useState(false);
|
||||||
|
const [selectedMessages, setSelectedMessages] = useState<Message[]>([]);
|
||||||
|
|
||||||
const messageData: Message[] = [
|
const messageData: Message[] = [
|
||||||
{ id: '1', title: <div className='text-blue-500'>پیش نویس</div>, status: <div className='text-yellow-500'>آرشیو شده</div>, sender: 'علی محمدی', date: '1402/05/12', read: true },
|
{ id: '1', title: 'اولین پیام', sender: 'علی محمدی', date: '1402/05/12', read: true },
|
||||||
{ id: '2', title: <div className='text-blue-500'>ارسالی</div>, status: <div className='text-yellow-500'>آرشیو شده</div>, sender: 'زهرا احمدی', date: '1402/05/13', read: true },
|
{ id: '2', title: 'دومین پیام', sender: 'زهرا احمدی', date: '1402/05/13', read: true },
|
||||||
{ id: '3', title: <div className='text-blue-500'>دریافت شده</div>, status: <div className='text-yellow-500'>آرشیو شده</div>, sender: 'مدیریت', date: '1402/05/14', read: true },
|
{ id: '3', title: 'جلسه هفتگی', sender: 'مدیریت', date: '1402/05/14', read: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
const columns: ColumnType<Message>[] = [
|
const columns: ColumnType<Message>[] = [
|
||||||
@@ -30,49 +31,94 @@ const ArchiveList: FC = () => {
|
|||||||
title: 'عنوان پیام',
|
title: 'عنوان پیام',
|
||||||
render: (item) => (
|
render: (item) => (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
{!item.read && <div className="w-2 h-2 rounded-full bg-blue-500 mr-2" />}
|
{!item.read && <div className="w-1.5 h-1.5 md:w-2 md:h-2 rounded-full bg-blue-500 mr-2" />}
|
||||||
<span>{item.title}</span>
|
<span className="truncate">{item.title}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{ key: 'status', title: 'وضعیت' },
|
{
|
||||||
{ key: 'sender', title: 'فرستنده' },
|
key: 'sender',
|
||||||
{ key: 'date', title: 'تاریخ', align: 'center' },
|
title: 'فرستنده',
|
||||||
|
render: (item) => (
|
||||||
|
<span className="truncate">{item.sender}</span>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'date',
|
||||||
|
title: 'تاریخ',
|
||||||
|
align: 'center',
|
||||||
|
render: (item) => (
|
||||||
|
<span className="text-xs md:text-sm">{item.date}</span>
|
||||||
|
)
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const tableActions = (
|
const tableActions = (
|
||||||
<div className='flex items-center gap-4'>
|
<div className='flex items-center gap-2 md:gap-4'>
|
||||||
<Refresh2 size={20} color='black' />
|
<Refresh2 size={18} color='black' className="cursor-pointer" />
|
||||||
<More size={20} color='black' className='rotate-90' />
|
<More size={18} color='black' className='rotate-90 cursor-pointer' />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const selectedActions = (
|
||||||
|
<>
|
||||||
|
<ArchiveSlash size={18} color='black' onClick={() => handleUnarchiveSelected()} className="cursor-pointer" />
|
||||||
|
<Trash size={18} color='black' onClick={() => handleDeleteSelected()} className="cursor-pointer" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
const handleFilterChange = (newFilters: FilterValues) => {
|
const handleFilterChange = (newFilters: FilterValues) => {
|
||||||
console.log('Applied filters:', newFilters);
|
console.log('Applied filters:', newFilters);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSelectionChange = (selectedRows: Message[]) => {
|
||||||
|
setSelectedMessages(selectedRows);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteSelected = () => {
|
||||||
|
console.log('حذف پیامهای انتخاب شده:', selectedMessages.map(m => m.id));
|
||||||
|
// اینجا منطق حذف را پیادهسازی کنید
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUnarchiveSelected = () => {
|
||||||
|
console.log('خروج از آرشیو پیامهای انتخاب شده:', selectedMessages.map(m => m.id));
|
||||||
|
// اینجا منطق خروج از آرشیو را پیادهسازی کنید
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRowActions = (message: Message): RowActionItem[] => [
|
||||||
|
{
|
||||||
|
label: 'خروج از آرشیو',
|
||||||
|
icon: <ArchiveSlash size={16} color="black" />,
|
||||||
|
onClick: () => console.log('خروج از آرشیو', message.id),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'حذف',
|
||||||
|
icon: <Trash size={16} color="red" />,
|
||||||
|
onClick: () => console.log('حذف پیام', message.id),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-4'>
|
<div className='mt-2 md:mt-4 px-2 md:px-0'>
|
||||||
<h1>{t('draft.title')}</h1>
|
<h1 className="text-lg md:text-xl xl:text-2xl font-bold mb-4 md:mb-0">{t('archive.title')}</h1>
|
||||||
|
|
||||||
<Filters
|
<Filters
|
||||||
fields={[
|
fields={[
|
||||||
{ type: 'date', name: 'from_date', placeholder: t('received.from_date') },
|
{ type: 'date', name: 'from_date', placeholder: t('archive.from_date') },
|
||||||
{ type: 'date', name: 'to_date', placeholder: t('received.to_date') },
|
{ type: 'date', name: 'to_date', placeholder: t('archive.to_date') },
|
||||||
{
|
{
|
||||||
type: 'select',
|
type: 'select',
|
||||||
name: 'status',
|
name: 'status',
|
||||||
placeholder: t('received.all'),
|
placeholder: t('archive.all'),
|
||||||
options: [
|
options: [
|
||||||
{ value: 'all', label: t('received.all') },
|
{ value: 'all', label: t('archive.all') },
|
||||||
{ value: 'read', label: t('received.read') },
|
{ value: 'read', label: t('archive.read') },
|
||||||
{ value: 'unread', label: t('received.unread') }
|
{ value: 'unread', label: t('archive.unread') }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ type: 'input', name: 'search', placeholder: t('received.search') }
|
{ type: 'input', name: 'search', placeholder: t('archive.search') }
|
||||||
]}
|
]}
|
||||||
onChange={handleFilterChange}
|
onChange={handleFilterChange}
|
||||||
className="mt-8 flex justify-between items-center"
|
|
||||||
searchField="search"
|
searchField="search"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -82,18 +128,20 @@ const ArchiveList: FC = () => {
|
|||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
showHeader={false}
|
showHeader={false}
|
||||||
actions={tableActions}
|
actions={tableActions}
|
||||||
|
selectedActions={selectedActions}
|
||||||
|
onSelectionChange={handleSelectionChange}
|
||||||
onRowClick={(id) => console.log(`کلیک روی پیام با شناسه ${id}`)}
|
onRowClick={(id) => console.log(`کلیک روی پیام با شناسه ${id}`)}
|
||||||
noDataMessage={
|
noDataMessage={
|
||||||
<div className="flex flex-col items-center py-6">
|
<div className="flex flex-col items-center py-6">
|
||||||
<InfoCircle size={40} className="text-gray-300 mb-2" />
|
<InfoCircle size={32} className="text-gray-300 mb-2" />
|
||||||
<div>هیچ پیامی یافت نشد</div>
|
<div className="text-sm">هیچ پیام آرشیو شدهای یافت نشد</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
selectable={true}
|
selectable={true}
|
||||||
|
rowActions={getRowActions}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ArchiveList
|
export default List
|
||||||
|
|||||||
+72
-32
@@ -1,13 +1,14 @@
|
|||||||
import Filters, { FilterValues } from '../../components/Filters';
|
import Filters, { FilterValues } from '../../components/Filters';
|
||||||
import { FC, ReactNode, useState } from 'react'
|
import { FC, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Table from '../../components/Table';
|
import Table from '../../components/Table';
|
||||||
import { ColumnType } from '../../components/types/TableTypes';
|
import { ColumnType } from '../../components/types/TableTypes';
|
||||||
import { InfoCircle, More, Refresh2 } from 'iconsax-react';
|
import { InfoCircle, More, Refresh2, Trash, Edit } from 'iconsax-react';
|
||||||
|
import { RowActionItem } from '../../components/RowActionsDropdown';
|
||||||
|
|
||||||
interface Message extends Record<string, unknown> {
|
interface Message extends Record<string, unknown> {
|
||||||
id: string;
|
id: string;
|
||||||
title: string | ReactNode;
|
title: string;
|
||||||
sender: string;
|
sender: string;
|
||||||
date: string;
|
date: string;
|
||||||
read: boolean;
|
read: boolean;
|
||||||
@@ -16,11 +17,12 @@ interface Message extends Record<string, unknown> {
|
|||||||
const List: FC = () => {
|
const List: FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [isLoading] = useState(false);
|
const [isLoading] = useState(false);
|
||||||
|
const [selectedMessages, setSelectedMessages] = useState<Message[]>([]);
|
||||||
|
|
||||||
const messageData: Message[] = [
|
const messageData: Message[] = [
|
||||||
{ id: '1', title: <div className='text-blue-500'>پیش نویس</div>, sender: 'علی محمدی', date: '1402/05/12', read: true },
|
{ id: '1', title: 'اولین پیام', sender: 'علی محمدی', date: '1402/05/12', read: true },
|
||||||
{ id: '2', title: <div className='text-blue-500'>پیش نویس</div>, sender: 'زهرا احمدی', date: '1402/05/13', read: true },
|
{ id: '2', title: 'دومین پیام', sender: 'زهرا احمدی', date: '1402/05/13', read: true },
|
||||||
{ id: '3', title: <div className='text-blue-500'>پیش نویس</div>, sender: 'مدیریت', date: '1402/05/14', read: true },
|
{ id: '3', title: 'جلسه هفتگی', sender: 'مدیریت', date: '1402/05/14', read: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
const columns: ColumnType<Message>[] = [
|
const columns: ColumnType<Message>[] = [
|
||||||
@@ -29,48 +31,84 @@ const List: FC = () => {
|
|||||||
title: 'عنوان پیام',
|
title: 'عنوان پیام',
|
||||||
render: (item) => (
|
render: (item) => (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
{!item.read && <div className="w-2 h-2 rounded-full bg-blue-500 mr-2" />}
|
{!item.read && <div className="w-1.5 h-1.5 md:w-2 md:h-2 rounded-full bg-blue-500 mr-2" />}
|
||||||
<span>{item.title}</span>
|
<span className="truncate">{item.title}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{ key: 'sender', title: 'فرستنده' },
|
{
|
||||||
{ key: 'date', title: 'تاریخ', align: 'center' },
|
key: 'sender',
|
||||||
|
title: 'فرستنده',
|
||||||
|
render: (item) => (
|
||||||
|
<span className="truncate">{item.sender}</span>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'date',
|
||||||
|
title: 'تاریخ',
|
||||||
|
align: 'center',
|
||||||
|
render: (item) => (
|
||||||
|
<span className="text-xs md:text-sm">{item.date}</span>
|
||||||
|
)
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const tableActions = (
|
const tableActions = (
|
||||||
<div className='flex items-center gap-4'>
|
<div className='flex items-center gap-2 md:gap-4'>
|
||||||
<Refresh2 size={20} color='black' />
|
<Refresh2 size={18} color='black' className="cursor-pointer" />
|
||||||
<More size={20} color='black' className='rotate-90' />
|
<More size={18} color='black' className='rotate-90 cursor-pointer' />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const selectedActions = (
|
||||||
|
<>
|
||||||
|
<Trash size={18} color='black' onClick={() => handleDeleteSelected()} className="cursor-pointer" />
|
||||||
|
<Edit size={18} color='black' onClick={() => handleEditSelected()} className="cursor-pointer" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
const handleFilterChange = (newFilters: FilterValues) => {
|
const handleFilterChange = (newFilters: FilterValues) => {
|
||||||
console.log('Applied filters:', newFilters);
|
console.log('Applied filters:', newFilters);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSelectionChange = (selectedRows: Message[]) => {
|
||||||
|
setSelectedMessages(selectedRows);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteSelected = () => {
|
||||||
|
console.log('حذف پیامهای انتخاب شده:', selectedMessages.map(m => m.id));
|
||||||
|
// اینجا منطق حذف را پیادهسازی کنید
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditSelected = () => {
|
||||||
|
console.log('ویرایش پیامهای انتخاب شده:', selectedMessages.map(m => m.id));
|
||||||
|
// اینجا منطق ویرایش را پیادهسازی کنید
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRowActions = (message: Message): RowActionItem[] => [
|
||||||
|
{
|
||||||
|
label: 'ویرایش',
|
||||||
|
icon: <Edit size={16} color="black" />,
|
||||||
|
onClick: () => console.log('ویرایش پیام', message.id),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'حذف',
|
||||||
|
icon: <Trash size={16} color="red" />,
|
||||||
|
onClick: () => console.log('حذف پیام', message.id),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-4'>
|
<div className='mt-2 md:mt-4 px-2 md:px-0'>
|
||||||
<h1>{t('draft.title')}</h1>
|
<h1 className="text-lg md:text-xl xl:text-2xl font-bold mb-4 md:mb-0">{t('draft.title')}</h1>
|
||||||
|
|
||||||
<Filters
|
<Filters
|
||||||
fields={[
|
fields={[
|
||||||
{ type: 'date', name: 'from_date', placeholder: t('received.from_date') },
|
{ type: 'date', name: 'from_date', placeholder: t('draft.from_date') },
|
||||||
{ type: 'date', name: 'to_date', placeholder: t('received.to_date') },
|
{ type: 'date', name: 'to_date', placeholder: t('draft.to_date') },
|
||||||
{
|
{ type: 'input', name: 'search', placeholder: t('draft.search') }
|
||||||
type: 'select',
|
|
||||||
name: 'status',
|
|
||||||
placeholder: t('received.all'),
|
|
||||||
options: [
|
|
||||||
{ value: 'all', label: t('received.all') },
|
|
||||||
{ value: 'read', label: t('received.read') },
|
|
||||||
{ value: 'unread', label: t('received.unread') }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{ type: 'input', name: 'search', placeholder: t('received.search') }
|
|
||||||
]}
|
]}
|
||||||
onChange={handleFilterChange}
|
onChange={handleFilterChange}
|
||||||
className="mt-8 flex justify-between items-center"
|
|
||||||
searchField="search"
|
searchField="search"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -80,16 +118,18 @@ const List: FC = () => {
|
|||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
showHeader={false}
|
showHeader={false}
|
||||||
actions={tableActions}
|
actions={tableActions}
|
||||||
|
selectedActions={selectedActions}
|
||||||
|
onSelectionChange={handleSelectionChange}
|
||||||
onRowClick={(id) => console.log(`کلیک روی پیام با شناسه ${id}`)}
|
onRowClick={(id) => console.log(`کلیک روی پیام با شناسه ${id}`)}
|
||||||
noDataMessage={
|
noDataMessage={
|
||||||
<div className="flex flex-col items-center py-6">
|
<div className="flex flex-col items-center py-6">
|
||||||
<InfoCircle size={40} className="text-gray-300 mb-2" />
|
<InfoCircle size={32} className="text-gray-300 mb-2" />
|
||||||
<div>هیچ پیامی یافت نشد</div>
|
<div className="text-sm">هیچ پیشنویسی یافت نشد</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
selectable={true}
|
selectable={true}
|
||||||
|
rowActions={getRowActions}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-1
@@ -2,7 +2,34 @@ import { FC } from 'react'
|
|||||||
|
|
||||||
const Home: FC = () => {
|
const Home: FC = () => {
|
||||||
return (
|
return (
|
||||||
<div>home page</div>
|
<div className='mt-2 md:mt-4 px-2 md:px-0'>
|
||||||
|
<div className="text-center py-12 md:py-20">
|
||||||
|
<h1 className="text-2xl md:text-3xl xl:text-4xl font-bold text-gray-800 mb-4">
|
||||||
|
خوش آمدید به سیستم مدیریت ایمیل
|
||||||
|
</h1>
|
||||||
|
<p className="text-sm md:text-base text-gray-600 mb-8 max-w-2xl mx-auto">
|
||||||
|
از منوی کناری میتوانید به بخشهای مختلف سیستم دسترسی داشته باشید
|
||||||
|
</p>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4 md:gap-6 mt-8 md:mt-12">
|
||||||
|
<div className="bg-white p-4 md:p-6 rounded-xl md:rounded-2xl shadow-sm border">
|
||||||
|
<h3 className="text-lg md:text-xl font-semibold mb-2">پیامهای دریافتی</h3>
|
||||||
|
<p className="text-xs md:text-sm text-gray-600">مدیریت ایمیلهای واردشده</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white p-4 md:p-6 rounded-xl md:rounded-2xl shadow-sm border">
|
||||||
|
<h3 className="text-lg md:text-xl font-semibold mb-2">پیامهای ارسالی</h3>
|
||||||
|
<p className="text-xs md:text-sm text-gray-600">مشاهده ایمیلهای ارسال شده</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white p-4 md:p-6 rounded-xl md:rounded-2xl shadow-sm border">
|
||||||
|
<h3 className="text-lg md:text-xl font-semibold mb-2">پیشنویسها</h3>
|
||||||
|
<p className="text-xs md:text-sm text-gray-600">ادامه نوشتن ایمیلهای ناتمام</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white p-4 md:p-6 rounded-xl md:rounded-2xl shadow-sm border">
|
||||||
|
<h3 className="text-lg md:text-xl font-semibold mb-2">تنظیمات</h3>
|
||||||
|
<p className="text-xs md:text-sm text-gray-600">پیکربندی سیستم و حساب کاربری</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+80
-13
@@ -3,7 +3,8 @@ import { FC, useState } from 'react'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Table from '../../components/Table';
|
import Table from '../../components/Table';
|
||||||
import { ColumnType } from '../../components/types/TableTypes';
|
import { ColumnType } from '../../components/types/TableTypes';
|
||||||
import { InfoCircle, More, Refresh2 } from 'iconsax-react';
|
import { InfoCircle, More, Refresh2, Trash, Edit, Archive, ArchiveSlash } from 'iconsax-react';
|
||||||
|
import { RowActionItem } from '../../components/RowActionsDropdown';
|
||||||
|
|
||||||
interface Message extends Record<string, unknown> {
|
interface Message extends Record<string, unknown> {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -16,6 +17,7 @@ interface Message extends Record<string, unknown> {
|
|||||||
const List: FC = () => {
|
const List: FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [isLoading] = useState(false);
|
const [isLoading] = useState(false);
|
||||||
|
const [selectedMessages, setSelectedMessages] = useState<Message[]>([]);
|
||||||
|
|
||||||
const messageData: Message[] = [
|
const messageData: Message[] = [
|
||||||
{ id: '1', title: 'اولین پیام', sender: 'علی محمدی', date: '1402/05/12', read: true },
|
{ id: '1', title: 'اولین پیام', sender: 'علی محمدی', date: '1402/05/12', read: true },
|
||||||
@@ -29,29 +31,87 @@ const List: FC = () => {
|
|||||||
title: 'عنوان پیام',
|
title: 'عنوان پیام',
|
||||||
render: (item) => (
|
render: (item) => (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
{!item.read && <div className="w-2 h-2 rounded-full bg-blue-500 mr-2" />}
|
{!item.read && <div className="w-1.5 h-1.5 md:w-2 md:h-2 rounded-full bg-blue-500 mr-2" />}
|
||||||
<span>{item.title}</span>
|
<span className="truncate">{item.title}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{ key: 'sender', title: 'فرستنده' },
|
{
|
||||||
{ key: 'date', title: 'تاریخ', align: 'center' },
|
key: 'sender',
|
||||||
|
title: 'فرستنده',
|
||||||
|
render: (item) => (
|
||||||
|
<span className="truncate">{item.sender}</span>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'date',
|
||||||
|
title: 'تاریخ',
|
||||||
|
align: 'center',
|
||||||
|
render: (item) => (
|
||||||
|
<span className="text-xs md:text-sm">{item.date}</span>
|
||||||
|
)
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const tableActions = (
|
const tableActions = (
|
||||||
<div className='flex items-center gap-4'>
|
<div className='flex items-center gap-2 md:gap-4'>
|
||||||
<Refresh2 size={20} color='black' />
|
<Refresh2 size={18} color='black' className="cursor-pointer" />
|
||||||
<More size={20} color='black' className='rotate-90' />
|
<More size={18} color='black' className='rotate-90 cursor-pointer' />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const selectedActions = (
|
||||||
|
<>
|
||||||
|
<Trash size={18} color='black' onClick={() => handleDeleteSelected()} className="cursor-pointer" />
|
||||||
|
<Archive size={18} color='black' onClick={() => handleArchiveSelected()} className="cursor-pointer" />
|
||||||
|
<Edit size={18} color='black' onClick={() => handleMarkAsReadSelected()} className="cursor-pointer" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
const handleFilterChange = (newFilters: FilterValues) => {
|
const handleFilterChange = (newFilters: FilterValues) => {
|
||||||
console.log('Applied filters:', newFilters);
|
console.log('Applied filters:', newFilters);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSelectionChange = (selectedRows: Message[]) => {
|
||||||
|
setSelectedMessages(selectedRows);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteSelected = () => {
|
||||||
|
console.log('حذف پیامهای انتخاب شده:', selectedMessages.map(m => m.id));
|
||||||
|
// اینجا منطق حذف را پیادهسازی کنید
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleArchiveSelected = () => {
|
||||||
|
console.log('بایگانی پیامهای انتخاب شده:', selectedMessages.map(m => m.id));
|
||||||
|
// اینجا منطق بایگانی را پیادهسازی کنید
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMarkAsReadSelected = () => {
|
||||||
|
console.log('علامتگذاری به عنوان خوانده شده:', selectedMessages.map(m => m.id));
|
||||||
|
// اینجا منطق علامتگذاری را پیادهسازی کنید
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRowActions = (message: Message): RowActionItem[] => [
|
||||||
|
{
|
||||||
|
label: 'خروج از آرشیو',
|
||||||
|
icon: <ArchiveSlash size={16} color="black" />,
|
||||||
|
onClick: () => console.log('خروج از آرشیو', message.id),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'خوانده شده',
|
||||||
|
icon: <Edit size={16} color="black" />,
|
||||||
|
onClick: () => console.log('علامتگذاری به عنوان خوانده شده', message.id),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'حذف',
|
||||||
|
icon: <Trash size={16} color="red" />,
|
||||||
|
onClick: () => console.log('حذف پیام', message.id),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-4'>
|
<div className='mt-2 md:mt-4 px-2 md:px-0'>
|
||||||
<h1>{t('received.title')}</h1>
|
<h1 className="text-lg md:text-xl xl:text-2xl font-bold mb-4 md:mb-0">{t('received.title')}</h1>
|
||||||
|
|
||||||
<Filters
|
<Filters
|
||||||
fields={[
|
fields={[
|
||||||
@@ -70,7 +130,6 @@ const List: FC = () => {
|
|||||||
{ type: 'input', name: 'search', placeholder: t('received.search') }
|
{ type: 'input', name: 'search', placeholder: t('received.search') }
|
||||||
]}
|
]}
|
||||||
onChange={handleFilterChange}
|
onChange={handleFilterChange}
|
||||||
className="mt-8 flex justify-between items-center"
|
|
||||||
searchField="search"
|
searchField="search"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -80,14 +139,22 @@ const List: FC = () => {
|
|||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
showHeader={false}
|
showHeader={false}
|
||||||
actions={tableActions}
|
actions={tableActions}
|
||||||
|
selectedActions={selectedActions}
|
||||||
|
onSelectionChange={handleSelectionChange}
|
||||||
onRowClick={(id) => console.log(`کلیک روی پیام با شناسه ${id}`)}
|
onRowClick={(id) => console.log(`کلیک روی پیام با شناسه ${id}`)}
|
||||||
noDataMessage={
|
noDataMessage={
|
||||||
<div className="flex flex-col items-center py-6">
|
<div className="flex flex-col items-center py-6">
|
||||||
<InfoCircle size={40} className="text-gray-300 mb-2" />
|
<InfoCircle size={32} className="text-gray-300 mb-2" />
|
||||||
<div>هیچ پیامی یافت نشد</div>
|
<div className="text-sm">هیچ پیامی یافت نشد</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
selectable={true}
|
selectable={true}
|
||||||
|
rowActions={getRowActions}
|
||||||
|
pagination={{
|
||||||
|
totalPages: 10,
|
||||||
|
currentPage: 1,
|
||||||
|
onPageChange: () => { }
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+24
-12
@@ -29,19 +29,32 @@ const List: FC = () => {
|
|||||||
title: 'عنوان پیام',
|
title: 'عنوان پیام',
|
||||||
render: (item) => (
|
render: (item) => (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
{!item.read && <div className="w-2 h-2 rounded-full bg-blue-500 mr-2" />}
|
{!item.read && <div className="w-1.5 h-1.5 md:w-2 md:h-2 rounded-full bg-blue-500 mr-2" />}
|
||||||
<span>{item.title}</span>
|
<span className="truncate">{item.title}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{ key: 'sender', title: 'فرستنده' },
|
{
|
||||||
{ key: 'date', title: 'تاریخ', align: 'center' },
|
key: 'sender',
|
||||||
|
title: 'فرستنده',
|
||||||
|
render: (item) => (
|
||||||
|
<span className="truncate">{item.sender}</span>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'date',
|
||||||
|
title: 'تاریخ',
|
||||||
|
align: 'center',
|
||||||
|
render: (item) => (
|
||||||
|
<span className="text-xs md:text-sm">{item.date}</span>
|
||||||
|
)
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const tableActions = (
|
const tableActions = (
|
||||||
<div className='flex items-center gap-4'>
|
<div className='flex items-center gap-2 md:gap-4'>
|
||||||
<Refresh2 size={20} color='black' />
|
<Refresh2 size={18} color='black' className="cursor-pointer" />
|
||||||
<More size={20} color='black' className='rotate-90' />
|
<More size={18} color='black' className='rotate-90 cursor-pointer' />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -50,8 +63,8 @@ const List: FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-4'>
|
<div className='mt-2 md:mt-4 px-2 md:px-0'>
|
||||||
<h1>{t('sent.title')}</h1>
|
<h1 className="text-lg md:text-xl xl:text-2xl font-bold mb-4 md:mb-0">{t('sent.title')}</h1>
|
||||||
|
|
||||||
<Filters
|
<Filters
|
||||||
fields={[
|
fields={[
|
||||||
@@ -70,7 +83,6 @@ const List: FC = () => {
|
|||||||
{ type: 'input', name: 'search', placeholder: t('sent.search') }
|
{ type: 'input', name: 'search', placeholder: t('sent.search') }
|
||||||
]}
|
]}
|
||||||
onChange={handleFilterChange}
|
onChange={handleFilterChange}
|
||||||
className="mt-8 flex justify-between items-center"
|
|
||||||
searchField="search"
|
searchField="search"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -83,8 +95,8 @@ const List: FC = () => {
|
|||||||
onRowClick={(id) => console.log(`کلیک روی پیام با شناسه ${id}`)}
|
onRowClick={(id) => console.log(`کلیک روی پیام با شناسه ${id}`)}
|
||||||
noDataMessage={
|
noDataMessage={
|
||||||
<div className="flex flex-col items-center py-6">
|
<div className="flex flex-col items-center py-6">
|
||||||
<InfoCircle size={40} className="text-gray-300 mb-2" />
|
<InfoCircle size={32} className="text-gray-300 mb-2" />
|
||||||
<div>هیچ پیامی یافت نشد</div>
|
<div className="text-sm">هیچ پیامی یافت نشد</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
selectable={true}
|
selectable={true}
|
||||||
|
|||||||
@@ -23,9 +23,13 @@ const Setting: FC = () => {
|
|||||||
return <Address />;
|
return <Address />;
|
||||||
case SettingTabEnum.SETTING_PERSONALITY:
|
case SettingTabEnum.SETTING_PERSONALITY:
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-row-reverse gap-6 mt-8'>
|
<div className='flex flex-col xl:flex-row-reverse gap-4 md:gap-6 mt-6 md:mt-8'>
|
||||||
<PersonalitySidebar />
|
<div className="xl:w-auto w-full">
|
||||||
<Personality />
|
<PersonalitySidebar />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<Personality />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
@@ -34,29 +38,29 @@ const Setting: FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-4'>
|
<div className='mt-2 md:mt-4 px-2 md:px-0'>
|
||||||
<h1 className='text-2xl font-bold text-right'>{t('setting.title')}</h1>
|
<h1 className='text-lg md:text-xl xl:text-2xl font-bold text-right mb-4 md:mb-0'>{t('setting.title')}</h1>
|
||||||
|
|
||||||
<div className='mt-7'>
|
<div className='mt-4 md:mt-7'>
|
||||||
<Tabs
|
<Tabs
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
icon: <Setting3 size={22} color={activeTab === SettingTabEnum.SETTING_MAIL_SERVER ? 'black' : '#8C90A3'} />,
|
icon: <Setting3 size={20} color={activeTab === SettingTabEnum.SETTING_MAIL_SERVER ? 'black' : '#8C90A3'} />,
|
||||||
label: t('setting.mail_server'),
|
label: t('setting.mail_server'),
|
||||||
value: SettingTabEnum.SETTING_MAIL_SERVER
|
value: SettingTabEnum.SETTING_MAIL_SERVER
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <Global size={22} color={activeTab === SettingTabEnum.SETTING_DOMAIN ? 'black' : '#8C90A3'} />,
|
icon: <Global size={20} color={activeTab === SettingTabEnum.SETTING_DOMAIN ? 'black' : '#8C90A3'} />,
|
||||||
label: t('setting.domain'),
|
label: t('setting.domain'),
|
||||||
value: SettingTabEnum.SETTING_DOMAIN
|
value: SettingTabEnum.SETTING_DOMAIN
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <People size={22} color={activeTab === SettingTabEnum.SETTING_ADDRESS ? 'black' : '#8C90A3'} />,
|
icon: <People size={20} color={activeTab === SettingTabEnum.SETTING_ADDRESS ? 'black' : '#8C90A3'} />,
|
||||||
label: t('setting.address'),
|
label: t('setting.address'),
|
||||||
value: SettingTabEnum.SETTING_ADDRESS
|
value: SettingTabEnum.SETTING_ADDRESS
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <Brush2 size={22} color={activeTab === SettingTabEnum.SETTING_PERSONALITY ? 'black' : '#8C90A3'} />,
|
icon: <Brush2 size={20} color={activeTab === SettingTabEnum.SETTING_PERSONALITY ? 'black' : '#8C90A3'} />,
|
||||||
label: t('setting.personality'),
|
label: t('setting.personality'),
|
||||||
value: SettingTabEnum.SETTING_PERSONALITY
|
value: SettingTabEnum.SETTING_PERSONALITY
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ const Header: FC = () => {
|
|||||||
}, [location.pathname]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='fixed z-10 right-4 left-4 xl:right-[285px] top-4 xl:h-16 h-12 flex items-center px-6 bg-white justify-between rounded-[32px] xl:w-[calc(100%-305px)]'>
|
<div className='fixed z-10 right-2 left-2 md:right-4 md:left-4 xl:right-[285px] top-2 md:top-4 xl:h-16 h-12 flex items-center px-3 md:px-6 bg-white justify-between rounded-[20px] md:rounded-[32px] xl:w-[calc(100%-305px)]'>
|
||||||
|
|
||||||
<div className='min-w-[270px] hidden xl:block'>
|
<div className='min-w-[200px] md:min-w-[270px] hidden xl:block'>
|
||||||
<Input
|
<Input
|
||||||
variant='search'
|
variant='search'
|
||||||
placeholder={t('header.search')}
|
placeholder={t('header.search')}
|
||||||
@@ -40,12 +40,12 @@ const Header: FC = () => {
|
|||||||
<HambergerMenu size={24} color='black' />
|
<HambergerMenu size={24} color='black' />
|
||||||
</div>
|
</div>
|
||||||
{/* <img src={LogoImage} className='h-6 xl:hidden block absolute right-0 left-0 mx-auto' /> */}
|
{/* <img src={LogoImage} className='h-6 xl:hidden block absolute right-0 left-0 mx-auto' /> */}
|
||||||
<div className='flex xl:gap-6 gap-4 items-center'>
|
<div className='flex xl:gap-6 gap-3 md:gap-4 items-center'>
|
||||||
<Link to={Paths.home}>
|
<Link to={Paths.home}>
|
||||||
<Element3 color='black' className='xl:size-[18px] size-[17px]' />
|
<Element3 color='black' className='size-4 md:size-[17px] xl:size-[18px]' />
|
||||||
</Link>
|
</Link>
|
||||||
<Link className='xl:hidden' to={Paths.home}>
|
<Link className='xl:hidden' to={Paths.home}>
|
||||||
<Wallet className='xl:size-[18px] size-[17px]' color='black' />
|
<Wallet className='size-4 md:size-[17px] xl:size-[18px]' color='black' />
|
||||||
</Link>
|
</Link>
|
||||||
<Link className='hidden xl:block' to={Paths.home}>
|
<Link className='hidden xl:block' to={Paths.home}>
|
||||||
<div className='flex items-center h-8 pl-2 rounded-full bg-[#EEF0F7]'>
|
<div className='flex items-center h-8 pl-2 rounded-full bg-[#EEF0F7]'>
|
||||||
|
|||||||
+4
-4
@@ -5,12 +5,12 @@ import Header from './Header'
|
|||||||
|
|
||||||
const Main: FC = () => {
|
const Main: FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className='p-4 overflow-hidden'>
|
<div className='p-2 md:p-4 overflow-hidden'>
|
||||||
<SideBar />
|
<SideBar />
|
||||||
<Header />
|
<Header />
|
||||||
<div className='flex-1 xl:ms-[269px] mt-[68px] xl:mt-[81px]'>
|
<div className='flex-1 ms-0 xl:ms-[269px] mt-[68px] xl:mt-[81px]'>
|
||||||
<div className={`overflow-auto w-[${window.innerWidth}] max-h-[calc(100vh-113px)]`}>
|
<div className='overflow-auto w-full max-h-[calc(100vh-113px)] md:max-h-[calc(100vh-113px)]'>
|
||||||
<div className='xl:pb-20 pb-32'>
|
<div className='pb-20 md:pb-24 xl:pb-20'>
|
||||||
<AppRouter />
|
<AppRouter />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+15
-12
@@ -23,32 +23,35 @@ const SideBar: FC = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{
|
{
|
||||||
openSidebar && <div className='fixed top-0 left-0 right-0 bottom-0 bg-black bg-opacity-50 z-10' onClick={() => setOpenSidebar(false)} />
|
openSidebar && <div className='fixed top-0 left-0 right-0 bottom-0 bg-black/50 z-20' onClick={() => setOpenSidebar(false)} />
|
||||||
}
|
}
|
||||||
<div
|
<div
|
||||||
className={clx(
|
className={clx(
|
||||||
'fixed xl:flex flex-1 translate-x-[400px] xl:translate-x-0 transition-all ease-in-out opacity-0 invisible xl:visible xl:opacity-100 xl:right-4 right-0 xl:top-4 top-0 xl:bottom-4 bottom-0 xl:rounded-[32px] w-[250px] bg-white flex-col py-12',
|
'fixed xl:flex flex-1 translate-x-[400px] xl:translate-x-0 transition-all ease-in-out opacity-0 invisible xl:visible xl:opacity-100 xl:right-2 xl:md:right-4 right-0 xl:top-2 xl:md:top-4 top-0 xl:bottom-2 xl:md:bottom-4 bottom-0 xl:rounded-[20px] xl:md:rounded-[32px] w-[250px] sm:w-[280px] xl:w-[250px] bg-white flex-col py-8 md:py-10 xl:py-12',
|
||||||
openSidebar && 'opacity-100 visible -translate-x-[0px] block z-40'
|
openSidebar && 'opacity-100 visible -translate-x-[0px] block z-40'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className='flex justify-center'>
|
<div className='flex justify-center px-4'>
|
||||||
<img src={LogoImage} className='w-[140px]' />
|
<img src={LogoImage} className='w-[120px] md:w-[140px]' />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex justify-center mt-14'>
|
<div className='flex justify-center mt-10 md:mt-14 px-4'>
|
||||||
<Button
|
<Button
|
||||||
className='bg-secondary font-normal text-primary w-fit px-6'
|
className='bg-secondary font-normal text-primary w-fit px-4 md:px-6 text-sm'
|
||||||
onClick={() => setOpenNewMessage(true)}
|
onClick={() => {
|
||||||
|
setOpenSidebar(false)
|
||||||
|
setOpenNewMessage(true)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className='flex gap-2'>
|
<div className='flex gap-2 items-center'>
|
||||||
<Add size={20} color='black' />
|
<Add size={18} color='black' />
|
||||||
<div>{t('sidebar.new_message')}</div>
|
<div>{t('sidebar.new_message')}</div>
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex-1 flex flex-col h-full overflow-y-auto no-scrollbar'>
|
<div className='flex-1 flex flex-col h-full overflow-y-auto no-scrollbar'>
|
||||||
<div className='mt-10 px-12 text-[#C3C7DD] text-sm font-bold'>
|
<div className='mt-8 md:mt-10 px-8 md:px-12 text-[#C3C7DD] text-sm font-bold'>
|
||||||
{t('sidebar.menu')}
|
{t('sidebar.menu')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -89,7 +92,7 @@ const SideBar: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-10 px-12 text-[#C3C7DD] text-sm font-bold'>
|
<div className='mt-8 md:mt-10 px-8 md:px-12 text-[#C3C7DD] text-sm font-bold'>
|
||||||
{t('sidebar.other')}
|
{t('sidebar.other')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -109,7 +112,7 @@ const SideBar: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex-1 flex flex-col justify-end mt-14 pb-8'>
|
<div className='flex-1 flex flex-col justify-end mt-10 md:mt-14 pb-6 md:pb-8'>
|
||||||
<div className='text-xs text-[#8C90A3]'>
|
<div className='text-xs text-[#8C90A3]'>
|
||||||
<SideBarItem
|
<SideBarItem
|
||||||
icon={<Setting2 variant={isActive(Paths.setting) ? 'Bold' : 'Outline'} color={isActive(Paths.setting) ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
icon={<Setting2 variant={isActive(Paths.setting) ? 'Bold' : 'Outline'} color={isActive(Paths.setting) ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||||
|
|||||||
@@ -21,17 +21,20 @@ const SideBarItem: FC<Props> = (props: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link onClick={props.isLogout ? handleLogout : undefined} to={props.link} className='flex text-xs gap-9 mt-4'>
|
<Link onClick={props.isLogout ? handleLogout : undefined} to={props.link} className='flex text-xs gap-6 md:gap-8 xl:gap-9 mt-3 md:mt-4 px-2 md:px-0'>
|
||||||
{
|
{
|
||||||
!props.isWithoutLine &&
|
!props.isWithoutLine &&
|
||||||
<div className={clx(
|
<div className={clx(
|
||||||
'w-1 bg-black h-6',
|
'w-1 bg-black h-5 md:h-6',
|
||||||
!props.isActive && 'invisible'
|
!props.isActive && 'invisible'
|
||||||
)}></div>
|
)}></div>
|
||||||
}
|
}
|
||||||
<div className='flex gap-2.5 items-center'>
|
<div className='flex gap-2 md:gap-2.5 items-center'>
|
||||||
{props.icon}
|
{props.icon}
|
||||||
<div className={props.isActive ? 'text-black' : ''}>
|
<div className={clx(
|
||||||
|
'text-xs md:text-sm',
|
||||||
|
props.isActive ? 'text-black font-medium' : ''
|
||||||
|
)}>
|
||||||
{props.title}
|
{props.title}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user