parent
0dbcf47d32
commit
cf2e2d8854
14 changed files with 882 additions and 0 deletions
@ -0,0 +1,294 @@ |
|||||||
|
import React from "react"; |
||||||
|
|
||||||
|
import { |
||||||
|
useTable, |
||||||
|
useGlobalFilter, |
||||||
|
useAsyncDebounce, |
||||||
|
useSortBy, |
||||||
|
usePagination, |
||||||
|
} from "react-table"; // new
|
||||||
|
|
||||||
|
import { |
||||||
|
ChevronDoubleLeftIcon, |
||||||
|
ChevronLeftIcon, |
||||||
|
ChevronRightIcon, |
||||||
|
ChevronDoubleRightIcon, |
||||||
|
} from "@heroicons/react/solid"; |
||||||
|
|
||||||
|
import { Button, PageButton } from "./shared/Button"; |
||||||
|
import { classNames } from "./shared/Utils"; |
||||||
|
import { SortIcon, SortUpIcon, SortDownIcon } from "./shared/Icons"; |
||||||
|
|
||||||
|
function GlobalFilter({ |
||||||
|
preGlobalFilteredRows, |
||||||
|
globalFilter, |
||||||
|
setGlobalFilter, |
||||||
|
}) { |
||||||
|
const count = preGlobalFilteredRows.length; |
||||||
|
const [value, setValue] = React.useState(globalFilter); |
||||||
|
const onChange = useAsyncDebounce((value) => { |
||||||
|
setGlobalFilter(value || undefined); |
||||||
|
}, 200); |
||||||
|
|
||||||
|
return ( |
||||||
|
<span> |
||||||
|
Search:{" "} |
||||||
|
<input |
||||||
|
value={value || ""} |
||||||
|
onChange={(e) => { |
||||||
|
setValue(e.target.value); |
||||||
|
onChange(e.target.value); |
||||||
|
}} |
||||||
|
placeholder={`${count} records...`} |
||||||
|
/> |
||||||
|
</span> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
// This is a custom filter UI for selecting
|
||||||
|
export function StatusPill({ value }) { |
||||||
|
// const status = value ? value.toLowerCase() : "unknown";
|
||||||
|
|
||||||
|
return ( |
||||||
|
// <span
|
||||||
|
// className={`p-4 whitespace-nowrap text-base font-light
|
||||||
|
// ${status ? "text-green-400" : "text-red-400"}`}
|
||||||
|
// // className={classNames(
|
||||||
|
// // "px-3 py-1 uppercase leading-wide font-bold text-xs rounded-full shadow-sm",
|
||||||
|
// // status ? "bg-green-600 text-green-800" : null,
|
||||||
|
// // status ? "bg-yellow-400 text-yellow-800" : null
|
||||||
|
// // )}
|
||||||
|
// >
|
||||||
|
// {/* {status} */}
|
||||||
|
// {status ? "فعال" : "غیر فعال"}
|
||||||
|
// </span>
|
||||||
|
<span |
||||||
|
className={`p-4 whitespace-nowrap text-base font-light
|
||||||
|
${value ? "text-green-400" : "text-red-400"}`}
|
||||||
|
> |
||||||
|
{value ? "فعال" : "غیر فعال"} |
||||||
|
</span> |
||||||
|
); |
||||||
|
console.log(value); |
||||||
|
} |
||||||
|
|
||||||
|
function Table({ columns, data }) { |
||||||
|
// Use the state and functions returned from useTable to build your UI
|
||||||
|
const { |
||||||
|
getTableProps, |
||||||
|
getTableBodyProps, |
||||||
|
headerGroups, |
||||||
|
rows, |
||||||
|
prepareRow, |
||||||
|
//new
|
||||||
|
page, // Instead of using 'rows', we'll use page,
|
||||||
|
// which has only the rows for the active page
|
||||||
|
|
||||||
|
// The rest of these things are super handy, too ;)
|
||||||
|
canPreviousPage, |
||||||
|
canNextPage, |
||||||
|
pageOptions, |
||||||
|
pageCount, |
||||||
|
gotoPage, |
||||||
|
nextPage, |
||||||
|
previousPage, |
||||||
|
setPageSize, |
||||||
|
|
||||||
|
state, // new
|
||||||
|
preGlobalFilteredRows, // new
|
||||||
|
setGlobalFilter, |
||||||
|
} = useTable( |
||||||
|
{ |
||||||
|
columns, |
||||||
|
data, |
||||||
|
}, |
||||||
|
useGlobalFilter, |
||||||
|
useSortBy, // new
|
||||||
|
usePagination |
||||||
|
); |
||||||
|
|
||||||
|
// Render the UI for your table
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div className="sm:flex sm:gap-x-2"> |
||||||
|
<GlobalFilter |
||||||
|
preGlobalFilteredRows={preGlobalFilteredRows} |
||||||
|
globalFilter={state.globalFilter} |
||||||
|
setGlobalFilter={setGlobalFilter} |
||||||
|
/> |
||||||
|
{headerGroups.map((headerGroup) => |
||||||
|
headerGroup.headers.map((column) => |
||||||
|
column.Filter ? ( |
||||||
|
<div className="mt-2 sm:mt-0" key={column.id}> |
||||||
|
{column.render("Filter")} |
||||||
|
</div> |
||||||
|
) : null |
||||||
|
) |
||||||
|
)} |
||||||
|
</div> |
||||||
|
{/* table */} |
||||||
|
<div className="mt-4 flex flex-col"> |
||||||
|
<div className="-my-2 overflow-x-auto -mx-4 sm:-mx-6 lg:-mx-8"> |
||||||
|
<div className="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"> |
||||||
|
<div className="shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"> |
||||||
|
<table |
||||||
|
{...getTableProps()} |
||||||
|
className="min-w-full divide-y divide-gray-200" |
||||||
|
> |
||||||
|
<thead className="bg-gray-50"> |
||||||
|
{headerGroups.map((headerGroup) => ( |
||||||
|
<tr {...headerGroup.getHeaderGroupProps()}> |
||||||
|
{headerGroup.headers.map((column) => ( |
||||||
|
// Add the sorting props to control sorting. For this example
|
||||||
|
// we can add them into the header props
|
||||||
|
<th |
||||||
|
scope="col" |
||||||
|
className="group px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider" |
||||||
|
{...column.getHeaderProps( |
||||||
|
column.getSortByToggleProps() |
||||||
|
)} |
||||||
|
> |
||||||
|
<div className="flex items-center justify-between"> |
||||||
|
{column.render("Header")} |
||||||
|
{/* Add a sort direction indicator */} |
||||||
|
<span> |
||||||
|
{column.isSorted ? ( |
||||||
|
column.isSortedDesc ? ( |
||||||
|
<SortDownIcon className="w-4 h-4 text-gray-400" /> |
||||||
|
) : ( |
||||||
|
<SortUpIcon className="w-4 h-4 text-gray-400" /> |
||||||
|
) |
||||||
|
) : ( |
||||||
|
<SortIcon className="w-4 h-4 text-gray-400 opacity-0 group-hover:opacity-100" /> |
||||||
|
)} |
||||||
|
</span> |
||||||
|
</div> |
||||||
|
</th> |
||||||
|
))} |
||||||
|
</tr> |
||||||
|
))} |
||||||
|
</thead> |
||||||
|
<tbody |
||||||
|
{...getTableBodyProps()} |
||||||
|
className="bg-white divide-y divide-gray-200" |
||||||
|
> |
||||||
|
{page.map((row, i) => { |
||||||
|
// new
|
||||||
|
prepareRow(row); |
||||||
|
return ( |
||||||
|
<tr {...row.getRowProps()}> |
||||||
|
{row.cells.map((cell) => { |
||||||
|
return ( |
||||||
|
<td |
||||||
|
{...cell.getCellProps()} |
||||||
|
className="px-6 py-4 whitespace-nowrap" |
||||||
|
role="cell" |
||||||
|
> |
||||||
|
{cell.column.Cell.name === "defaultRenderer" ? ( |
||||||
|
<div className="text-sm text-gray-500"> |
||||||
|
{cell.render("Cell")} |
||||||
|
</div> |
||||||
|
) : ( |
||||||
|
cell.render("Cell") |
||||||
|
)} |
||||||
|
</td> |
||||||
|
); |
||||||
|
})} |
||||||
|
</tr> |
||||||
|
); |
||||||
|
})} |
||||||
|
</tbody> |
||||||
|
</table> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
{/* Pagination */} |
||||||
|
<div className="py-3 flex items-center justify-between"> |
||||||
|
<div className="flex-1 flex justify-between sm:hidden"> |
||||||
|
<Button onClick={() => previousPage()} disabled={!canPreviousPage}> |
||||||
|
Previous |
||||||
|
</Button> |
||||||
|
<Button onClick={() => nextPage()} disabled={!canNextPage}> |
||||||
|
Next |
||||||
|
</Button> |
||||||
|
</div> |
||||||
|
<div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between"> |
||||||
|
{/* pagintaion */} |
||||||
|
<div className="flex gap-x-2 items-baseline"> |
||||||
|
<span className="text-sm text-gray-700"> |
||||||
|
صفحه <span className="font-medium">{state.pageIndex + 1}</span> از{" "} |
||||||
|
<span className="font-medium">{pageOptions.length}</span> |
||||||
|
</span> |
||||||
|
<label> |
||||||
|
<span className="sr-only">ایتم های صفحه</span> |
||||||
|
<select |
||||||
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" |
||||||
|
value={state.pageSize} |
||||||
|
onChange={(e) => { |
||||||
|
setPageSize(Number(e.target.value)); |
||||||
|
}} |
||||||
|
> |
||||||
|
{[5, 10, 20].map((pageSize) => ( |
||||||
|
<option key={pageSize} value={pageSize}> |
||||||
|
نمایش {pageSize} |
||||||
|
</option> |
||||||
|
))} |
||||||
|
</select> |
||||||
|
</label> |
||||||
|
</div> |
||||||
|
{/* arrows */} |
||||||
|
<div> |
||||||
|
<nav |
||||||
|
className="relative z-0 inline-flex rounded-md shadow-sm -space-x-px" |
||||||
|
aria-label="Pagination" |
||||||
|
> |
||||||
|
<PageButton |
||||||
|
className="rounded-r-md" |
||||||
|
onClick={() => gotoPage(pageCount - 1)} |
||||||
|
disabled={!canNextPage} |
||||||
|
> |
||||||
|
<span className="sr-only">Last</span> |
||||||
|
<ChevronDoubleRightIcon |
||||||
|
className="h-5 w-5 text-gray-400" |
||||||
|
aria-hidden="true" |
||||||
|
/> |
||||||
|
</PageButton> |
||||||
|
<PageButton onClick={() => nextPage()} disabled={!canNextPage}> |
||||||
|
<span className="sr-only">Next</span> |
||||||
|
<ChevronRightIcon |
||||||
|
className="h-5 w-5 text-gray-400" |
||||||
|
aria-hidden="true" |
||||||
|
/> |
||||||
|
</PageButton> |
||||||
|
|
||||||
|
<PageButton |
||||||
|
onClick={() => previousPage()} |
||||||
|
disabled={!canPreviousPage} |
||||||
|
> |
||||||
|
<span className="sr-only">Previous</span> |
||||||
|
<ChevronLeftIcon |
||||||
|
className="h-5 w-5 text-gray-400" |
||||||
|
aria-hidden="true" |
||||||
|
/> |
||||||
|
</PageButton> |
||||||
|
<PageButton |
||||||
|
className="rounded-l-md" |
||||||
|
onClick={() => gotoPage(0)} |
||||||
|
disabled={!canPreviousPage} |
||||||
|
> |
||||||
|
<span className="sr-only">First</span> |
||||||
|
<ChevronDoubleLeftIcon |
||||||
|
className="h-5 w-5 text-gray-400" |
||||||
|
aria-hidden="true" |
||||||
|
/> |
||||||
|
</PageButton> |
||||||
|
</nav> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
export default Table; |
@ -0,0 +1,58 @@ |
|||||||
|
import React from "react"; |
||||||
|
import Table, { AvatarCell, SelectColumnFilter, StatusPill } from "./Table"; // new
|
||||||
|
|
||||||
|
const getData = () => { |
||||||
|
const data = [ |
||||||
|
{ |
||||||
|
name: "مایکروویر سامسونگ مدل ", |
||||||
|
status: true, |
||||||
|
date: "1400 / 09 / 21", |
||||||
|
price: 20000, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "مایکروویر سامسونگ مدل ", |
||||||
|
status: false, |
||||||
|
date: "1400 / 09 / 21", |
||||||
|
price: 20000, |
||||||
|
}, |
||||||
|
]; |
||||||
|
return [...data, ...data, ...data]; |
||||||
|
}; |
||||||
|
|
||||||
|
function ReactTable() { |
||||||
|
const columns = React.useMemo( |
||||||
|
() => [ |
||||||
|
{ |
||||||
|
Header: "عنوان", |
||||||
|
accessor: "name", |
||||||
|
}, |
||||||
|
{ |
||||||
|
Header: "وضعیت", |
||||||
|
accessor: "status", |
||||||
|
}, |
||||||
|
{ |
||||||
|
Header: "تاریخ", |
||||||
|
accessor: "date", |
||||||
|
}, |
||||||
|
{ |
||||||
|
Header: "مبلغ پرداختی", |
||||||
|
accessor: "price", |
||||||
|
}, |
||||||
|
], |
||||||
|
[] |
||||||
|
); |
||||||
|
|
||||||
|
const data = React.useMemo(() => getData(), []); |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="min-h-screen bg-gray-100 text-gray-900"> |
||||||
|
<main className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 pt-4"> |
||||||
|
<div className="mt-4"> |
||||||
|
<Table columns={columns} data={data} /> |
||||||
|
</div> |
||||||
|
</main> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
export default ReactTable; |
@ -0,0 +1,35 @@ |
|||||||
|
import React from 'react' |
||||||
|
import { classNames } from './Utils' |
||||||
|
|
||||||
|
export function Button({ children, className, ...rest }) { |
||||||
|
return ( |
||||||
|
<button |
||||||
|
type="button" |
||||||
|
className={ |
||||||
|
classNames( |
||||||
|
"relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...rest} |
||||||
|
> |
||||||
|
{children} |
||||||
|
</button> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export function PageButton({ children, className, ...rest }) { |
||||||
|
return ( |
||||||
|
<button |
||||||
|
type="button" |
||||||
|
className={ |
||||||
|
classNames( |
||||||
|
"relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...rest} |
||||||
|
> |
||||||
|
{children} |
||||||
|
</button> |
||||||
|
) |
||||||
|
} |
||||||
|
|
@ -0,0 +1,17 @@ |
|||||||
|
export function SortIcon({ className }) { |
||||||
|
return ( |
||||||
|
<svg className={className} stroke="currentColor" fill="currentColor" strokeWidth="0" viewBox="0 0 320 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M41 288h238c21.4 0 32.1 25.9 17 41L177 448c-9.4 9.4-24.6 9.4-33.9 0L24 329c-15.1-15.1-4.4-41 17-41zm255-105L177 64c-9.4-9.4-24.6-9.4-33.9 0L24 183c-15.1 15.1-4.4 41 17 41h238c21.4 0 32.1-25.9 17-41z"></path></svg> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export function SortUpIcon({ className }) { |
||||||
|
return ( |
||||||
|
<svg className={className} stroke="currentColor" fill="currentColor" strokeWidth="0" viewBox="0 0 320 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M279 224H41c-21.4 0-32.1-25.9-17-41L143 64c9.4-9.4 24.6-9.4 33.9 0l119 119c15.2 15.1 4.5 41-16.9 41z"></path></svg> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export function SortDownIcon({ className }) { |
||||||
|
return ( |
||||||
|
<svg className={className} stroke="currentColor" fill="currentColor" strokeWidth="0" viewBox="0 0 320 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M41 288h238c21.4 0 32.1 25.9 17 41L177 448c-9.4 9.4-24.6 9.4-33.9 0L24 329c-15.1-15.1-4.4-41 17-41z"></path></svg> |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,3 @@ |
|||||||
|
export function classNames(...classes) { |
||||||
|
return classes.filter(Boolean).join(" "); |
||||||
|
} |
@ -0,0 +1,305 @@ |
|||||||
|
import React from "react"; |
||||||
|
import { |
||||||
|
useTable, |
||||||
|
useFilters, |
||||||
|
useGlobalFilter, |
||||||
|
useAsyncDebounce, |
||||||
|
useSortBy, |
||||||
|
usePagination, |
||||||
|
} from "react-table"; |
||||||
|
import { |
||||||
|
ChevronDoubleLeftIcon, |
||||||
|
ChevronLeftIcon, |
||||||
|
ChevronRightIcon, |
||||||
|
ChevronDoubleRightIcon, |
||||||
|
} from "@heroicons/react/solid"; |
||||||
|
import { Button, PageButton } from "./shared/Button"; |
||||||
|
import { classNames } from "./shared/Utils"; |
||||||
|
import { SortIcon, SortUpIcon, SortDownIcon } from "./shared/Icons"; |
||||||
|
|
||||||
|
// Define a default UI for filtering
|
||||||
|
function GlobalFilter({ |
||||||
|
preGlobalFilteredRows, |
||||||
|
globalFilter, |
||||||
|
setGlobalFilter, |
||||||
|
}) { |
||||||
|
const count = preGlobalFilteredRows.length; |
||||||
|
const [value, setValue] = React.useState(globalFilter); |
||||||
|
const onChange = useAsyncDebounce((value) => { |
||||||
|
setGlobalFilter(value || undefined); |
||||||
|
}, 200); |
||||||
|
|
||||||
|
return ( |
||||||
|
<label className="flex gap-x-2 items-baseline"> |
||||||
|
<span className="text-gray-700">Search: </span> |
||||||
|
<input |
||||||
|
type="text" |
||||||
|
className="rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" |
||||||
|
value={value || ""} |
||||||
|
onChange={(e) => { |
||||||
|
setValue(e.target.value); |
||||||
|
onChange(e.target.value); |
||||||
|
}} |
||||||
|
placeholder={`${count} records...`} |
||||||
|
/> |
||||||
|
</label> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
export function StatusPill({ value }) { |
||||||
|
// const status = value ? value.toLowerCase() : "unknown";
|
||||||
|
|
||||||
|
return ( |
||||||
|
<span |
||||||
|
className={`p-4 whitespace-nowrap text-base font-light
|
||||||
|
${value ? "text-green-400" : "text-red-400"}`}
|
||||||
|
> |
||||||
|
{value ? "فعال" : "غیر فعال"} |
||||||
|
</span> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
// export function AvatarCell({ value, column, row }) {
|
||||||
|
// return (
|
||||||
|
// <div className="flex items-center">
|
||||||
|
// {/* برای عکس */}
|
||||||
|
// {/* <div className="flex-shrink-0 h-10 w-10">
|
||||||
|
// <img
|
||||||
|
// className="h-10 w-10 rounded-full"
|
||||||
|
// src={row.original[column.imgAccessor]}
|
||||||
|
// alt=""
|
||||||
|
// />
|
||||||
|
// </div> */}
|
||||||
|
// {/* اگر بخواهیم در کنار عنوان یک محتوا دیگه ای بنویسیم */}
|
||||||
|
// {/* <div className="ml-4">
|
||||||
|
// <div className="text-sm font-medium text-gray-900">{value}</div>
|
||||||
|
// <div className="text-sm text-gray-500">
|
||||||
|
// {row.original[column.emailAccessor]}
|
||||||
|
// </div>
|
||||||
|
// </div> */}
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
function Table({ columns, data }) { |
||||||
|
// Use the state and functions returned from useTable to build your UI
|
||||||
|
const { |
||||||
|
getTableProps, |
||||||
|
getTableBodyProps, |
||||||
|
headerGroups, |
||||||
|
prepareRow, |
||||||
|
page, // Instead of using 'rows', we'll use page,
|
||||||
|
// which has only the rows for the active page
|
||||||
|
|
||||||
|
// The rest of these things are super handy, too ;)
|
||||||
|
canPreviousPage, |
||||||
|
canNextPage, |
||||||
|
pageOptions, |
||||||
|
pageCount, |
||||||
|
gotoPage, |
||||||
|
nextPage, |
||||||
|
previousPage, |
||||||
|
setPageSize, |
||||||
|
|
||||||
|
state, |
||||||
|
preGlobalFilteredRows, |
||||||
|
setGlobalFilter, |
||||||
|
} = useTable( |
||||||
|
{ |
||||||
|
columns, |
||||||
|
data, |
||||||
|
}, |
||||||
|
useFilters, // useFilters!
|
||||||
|
useGlobalFilter, |
||||||
|
useSortBy, |
||||||
|
usePagination // new
|
||||||
|
); |
||||||
|
|
||||||
|
// Render the UI for your table
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div className="sm:flex sm:gap-x-2"> |
||||||
|
<GlobalFilter |
||||||
|
preGlobalFilteredRows={preGlobalFilteredRows} |
||||||
|
globalFilter={state.globalFilter} |
||||||
|
setGlobalFilter={setGlobalFilter} |
||||||
|
/> |
||||||
|
{headerGroups.map((headerGroup) => |
||||||
|
headerGroup.headers.map((column) => |
||||||
|
column.Filter ? ( |
||||||
|
<div className="mt-2 sm:mt-0" key={column.id}> |
||||||
|
{column.render("Filter")} |
||||||
|
</div> |
||||||
|
) : null |
||||||
|
) |
||||||
|
)} |
||||||
|
</div> |
||||||
|
{/* table */} |
||||||
|
<div className="mt-4 flex flex-col"> |
||||||
|
<div className="-my-2 overflow-x-auto -mx-4 sm:-mx-6 lg:-mx-8"> |
||||||
|
<div className="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"> |
||||||
|
<div className="shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"> |
||||||
|
<table |
||||||
|
{...getTableProps()} |
||||||
|
className="min-w-full divide-y divide-gray-200" |
||||||
|
> |
||||||
|
<thead className="bg-gray-50"> |
||||||
|
{headerGroups.map((headerGroup) => ( |
||||||
|
<tr {...headerGroup.getHeaderGroupProps()}> |
||||||
|
{headerGroup.headers.map((column) => ( |
||||||
|
// Add the sorting props to control sorting. For this example
|
||||||
|
// we can add them into the header props
|
||||||
|
<th |
||||||
|
scope="col" |
||||||
|
className="group px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider" |
||||||
|
{...column.getHeaderProps( |
||||||
|
column.getSortByToggleProps() |
||||||
|
)} |
||||||
|
> |
||||||
|
<div className="flex items-center justify-between"> |
||||||
|
{column.render("Header")} |
||||||
|
{/* Add a sort direction indicator */} |
||||||
|
<span> |
||||||
|
{column.isSorted ? ( |
||||||
|
column.isSortedDesc ? ( |
||||||
|
<SortDownIcon className="w-4 h-4 text-gray-400" /> |
||||||
|
) : ( |
||||||
|
<SortUpIcon className="w-4 h-4 text-gray-400" /> |
||||||
|
) |
||||||
|
) : ( |
||||||
|
<SortIcon className="w-4 h-4 text-gray-400 opacity-0 group-hover:opacity-100" /> |
||||||
|
)} |
||||||
|
</span> |
||||||
|
</div> |
||||||
|
</th> |
||||||
|
))} |
||||||
|
</tr> |
||||||
|
))} |
||||||
|
</thead> |
||||||
|
<tbody |
||||||
|
{...getTableBodyProps()} |
||||||
|
className="bg-white divide-y divide-gray-200" |
||||||
|
> |
||||||
|
{page.map((row, i) => { |
||||||
|
// new
|
||||||
|
prepareRow(row); |
||||||
|
return ( |
||||||
|
<tr {...row.getRowProps()}> |
||||||
|
{row.cells.map((cell) => { |
||||||
|
return ( |
||||||
|
<td |
||||||
|
{...cell.getCellProps()} |
||||||
|
className="px-6 py-4 whitespace-nowrap" |
||||||
|
role="cell" |
||||||
|
> |
||||||
|
{cell.column.Cell.name === "defaultRenderer" ? ( |
||||||
|
<div className="text-sm text-gray-500"> |
||||||
|
{cell.render("Cell")} |
||||||
|
</div> |
||||||
|
) : ( |
||||||
|
cell.render("Cell") |
||||||
|
)} |
||||||
|
</td> |
||||||
|
); |
||||||
|
})} |
||||||
|
</tr> |
||||||
|
); |
||||||
|
})} |
||||||
|
</tbody> |
||||||
|
</table> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
{/* Pagination */} |
||||||
|
<div className="py-3 flex items-center justify-between"> |
||||||
|
<div className="flex-1 flex justify-between sm:hidden"> |
||||||
|
<Button onClick={() => previousPage()} disabled={!canPreviousPage}> |
||||||
|
Previous |
||||||
|
</Button> |
||||||
|
<Button onClick={() => nextPage()} disabled={!canNextPage}> |
||||||
|
Next |
||||||
|
</Button> |
||||||
|
</div> |
||||||
|
<div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between"> |
||||||
|
{/* numbers */} |
||||||
|
<div className="flex gap-x-2 items-baseline"> |
||||||
|
<span className="text-sm text-gray-700"> |
||||||
|
صفحه <span className="font-medium">{state.pageIndex + 1}</span> از |
||||||
|
<span className="font-medium">{pageOptions.length}</span> |
||||||
|
</span> |
||||||
|
<label> |
||||||
|
<span className="sr-only">ایتم های موجود در هر صفحه</span> |
||||||
|
<select |
||||||
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" |
||||||
|
value={state.pageSize} |
||||||
|
onChange={(e) => { |
||||||
|
setPageSize(Number(e.target.value)); |
||||||
|
}} |
||||||
|
> |
||||||
|
{[5, 10, 20].map((pageSize) => ( |
||||||
|
<option key={pageSize} value={pageSize}> |
||||||
|
نمایش {pageSize} |
||||||
|
</option> |
||||||
|
))} |
||||||
|
</select> |
||||||
|
</label> |
||||||
|
</div> |
||||||
|
{/* arrows */} |
||||||
|
<div> |
||||||
|
<nav |
||||||
|
className="relative z-0 inline-flex rounded-md shadow-sm -space-x-px" |
||||||
|
aria-label="Pagination" |
||||||
|
> |
||||||
|
<PageButton |
||||||
|
className="rounded-l-md" |
||||||
|
onClick={() => gotoPage(0)} |
||||||
|
disabled={!canPreviousPage} |
||||||
|
> |
||||||
|
<span className="sr-only">First</span> |
||||||
|
<ChevronDoubleRightIcon |
||||||
|
className="h-5 w-5 text-gray-400" |
||||||
|
aria-hidden="true" |
||||||
|
/> |
||||||
|
</PageButton> |
||||||
|
|
||||||
|
<PageButton |
||||||
|
onClick={() => previousPage()} |
||||||
|
disabled={!canPreviousPage} |
||||||
|
> |
||||||
|
<span className="sr-only">Previous</span> |
||||||
|
<ChevronRightIcon |
||||||
|
ChevronLeftIcon |
||||||
|
className="h-5 w-5 text-gray-400" |
||||||
|
aria-hidden="true" |
||||||
|
/> |
||||||
|
</PageButton> |
||||||
|
|
||||||
|
<PageButton onClick={() => nextPage()} disabled={!canNextPage}> |
||||||
|
<span className="sr-only"> Next</span> |
||||||
|
<ChevronLeftIcon |
||||||
|
className="h-5 w-5 text-gray-400" |
||||||
|
aria-hidden="true" |
||||||
|
/> |
||||||
|
</PageButton> |
||||||
|
|
||||||
|
<PageButton |
||||||
|
className="rounded-r-md" |
||||||
|
onClick={() => gotoPage(pageCount - 1)} |
||||||
|
disabled={!canNextPage} |
||||||
|
> |
||||||
|
<span className="sr-only">Last</span> |
||||||
|
<ChevronDoubleLeftIcon |
||||||
|
className="h-5 w-5 text-gray-400" |
||||||
|
aria-hidden="true" |
||||||
|
/> |
||||||
|
</PageButton> |
||||||
|
</nav> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
export default Table; |
@ -0,0 +1,90 @@ |
|||||||
|
import React from "react"; |
||||||
|
import Table, { AvatarCell, SelectColumnFilter, StatusPill } from "./Table"; // new
|
||||||
|
|
||||||
|
const getData = () => { |
||||||
|
const data = [ |
||||||
|
{ |
||||||
|
name: "مایکروویر سامسونگ مدل ", |
||||||
|
// email: "jane.cooper@example.com",
|
||||||
|
status: true, |
||||||
|
date: "1400/09/21", |
||||||
|
price: 20000, |
||||||
|
// imgUrl:
|
||||||
|
// "https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=4&w=256&h=256&q=60",
|
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "آزمون آنلاین درس علوم تجربی ", |
||||||
|
status: false, |
||||||
|
date: "1400/09/21", |
||||||
|
price: 18000, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "مایکروویر سامسونگ مدل ", |
||||||
|
status: true, |
||||||
|
date: "1400/09/21", |
||||||
|
price: 20000, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "آزمون آنلاین درس علوم تجربی ", |
||||||
|
status: false, |
||||||
|
date: "1400/09/21", |
||||||
|
price: 18000, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "مایکروویر سامسونگ مدل ", |
||||||
|
status: true, |
||||||
|
date: "1400/09/21", |
||||||
|
price: 27000000, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "آزمون آنلاین درس علوم تجربی ", |
||||||
|
status: false, |
||||||
|
date: "1400/09/21", |
||||||
|
price: 1000, |
||||||
|
}, |
||||||
|
]; |
||||||
|
return [...data, ...data, ...data]; |
||||||
|
}; |
||||||
|
|
||||||
|
const ReactTable2 = () => { |
||||||
|
const columns = React.useMemo( |
||||||
|
() => [ |
||||||
|
{ |
||||||
|
Header: "عنوان", |
||||||
|
accessor: "name", |
||||||
|
// اگر بخواهیم عکسی در جدول نشان بدیم یا یک محتوایی در کنار عنوان داشته باشیم اول باید اواتار سل رو در فایل تیبل جی اس ان کامنت کنیم و بعد ...
|
||||||
|
// Cell: AvatarCell,
|
||||||
|
// imgAccessor: "imgUrl",
|
||||||
|
// emailAccessor: "email",
|
||||||
|
}, |
||||||
|
{ |
||||||
|
Header: "وضعیت", |
||||||
|
accessor: "status", |
||||||
|
Cell: StatusPill, |
||||||
|
}, |
||||||
|
{ |
||||||
|
Header: "تاریخ", |
||||||
|
accessor: "date", |
||||||
|
}, |
||||||
|
{ |
||||||
|
Header: "مبلغ پرداختی", |
||||||
|
accessor: "price", |
||||||
|
}, |
||||||
|
], |
||||||
|
[] |
||||||
|
); |
||||||
|
|
||||||
|
const data = React.useMemo(() => getData(), []); |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="min-h-screen bg-gray-100 text-gray-900"> |
||||||
|
<main className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 pt-4"> |
||||||
|
<div className="my-6"> |
||||||
|
<Table columns={columns} data={data} /> |
||||||
|
</div> |
||||||
|
</main> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default ReactTable2; |
@ -0,0 +1,35 @@ |
|||||||
|
import React from 'react' |
||||||
|
import { classNames } from './Utils' |
||||||
|
|
||||||
|
export function Button({ children, className, ...rest }) { |
||||||
|
return ( |
||||||
|
<button |
||||||
|
type="button" |
||||||
|
className={ |
||||||
|
classNames( |
||||||
|
"relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...rest} |
||||||
|
> |
||||||
|
{children} |
||||||
|
</button> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export function PageButton({ children, className, ...rest }) { |
||||||
|
return ( |
||||||
|
<button |
||||||
|
type="button" |
||||||
|
className={ |
||||||
|
classNames( |
||||||
|
"relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50", |
||||||
|
className |
||||||
|
)} |
||||||
|
{...rest} |
||||||
|
> |
||||||
|
{children} |
||||||
|
</button> |
||||||
|
) |
||||||
|
} |
||||||
|
|
@ -0,0 +1,17 @@ |
|||||||
|
export function SortIcon({ className }) { |
||||||
|
return ( |
||||||
|
<svg className={className} stroke="currentColor" fill="currentColor" strokeWidth="0" viewBox="0 0 320 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M41 288h238c21.4 0 32.1 25.9 17 41L177 448c-9.4 9.4-24.6 9.4-33.9 0L24 329c-15.1-15.1-4.4-41 17-41zm255-105L177 64c-9.4-9.4-24.6-9.4-33.9 0L24 183c-15.1 15.1-4.4 41 17 41h238c21.4 0 32.1-25.9 17-41z"></path></svg> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export function SortUpIcon({ className }) { |
||||||
|
return ( |
||||||
|
<svg className={className} stroke="currentColor" fill="currentColor" strokeWidth="0" viewBox="0 0 320 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M279 224H41c-21.4 0-32.1-25.9-17-41L143 64c9.4-9.4 24.6-9.4 33.9 0l119 119c15.2 15.1 4.5 41-16.9 41z"></path></svg> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
export function SortDownIcon({ className }) { |
||||||
|
return ( |
||||||
|
<svg className={className} stroke="currentColor" fill="currentColor" strokeWidth="0" viewBox="0 0 320 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M41 288h238c21.4 0 32.1 25.9 17 41L177 448c-9.4 9.4-24.6 9.4-33.9 0L24 329c-15.1-15.1-4.4-41 17-41z"></path></svg> |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,3 @@ |
|||||||
|
export function classNames(...classes) { |
||||||
|
return classes.filter(Boolean).join(" "); |
||||||
|
} |
Loading…
Reference in new issue