757 lines
21 KiB
JavaScript
757 lines
21 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import { classNames } from 'primereact/utils';
|
|
import { FilterMatchMode, FilterOperator } from 'primereact/api';
|
|
import { DataTable } from 'primereact/datatable';
|
|
import { Column } from 'primereact/column';
|
|
import { Dropdown } from 'primereact/dropdown';
|
|
import { InputNumber } from 'primereact/inputnumber';
|
|
import { Button } from 'primereact/button';
|
|
import { ProgressBar } from 'primereact/progressbar';
|
|
import { Calendar } from 'primereact/calendar';
|
|
import { MultiSelect } from 'primereact/multiselect';
|
|
import { Slider } from 'primereact/slider';
|
|
import { TriStateCheckbox } from 'primereact/tristatecheckbox';
|
|
import { ToggleButton } from 'primereact/togglebutton';
|
|
import { Rating } from 'primereact/rating';
|
|
import { CustomerService } from '../service/CustomerService';
|
|
import { ProductService } from '../service/ProductService';
|
|
|
|
const TableDemo = () => {
|
|
const [customers1, setCustomers1] = useState(null);
|
|
const [customers2, setCustomers2] = useState([]);
|
|
const [customers3, setCustomers3] = useState([]);
|
|
const [filters1, setFilters1] = useState(null);
|
|
const [loading1, setLoading1] = useState(true);
|
|
const [loading2, setLoading2] = useState(true);
|
|
const [idFrozen, setIdFrozen] = useState(false);
|
|
const [products, setProducts] = useState([]);
|
|
const [expandedRows, setExpandedRows] = useState(null);
|
|
|
|
const representatives = [
|
|
{ name: 'Amy Elsner', image: 'amyelsner.png' },
|
|
{ name: 'Anna Fali', image: 'annafali.png' },
|
|
{ name: 'Asiya Javayant', image: 'asiyajavayant.png' },
|
|
{ name: 'Bernardo Dominic', image: 'bernardodominic.png' },
|
|
{ name: 'Elwin Sharvill', image: 'elwinsharvill.png' },
|
|
{ name: 'Ioni Bowcher', image: 'ionibowcher.png' },
|
|
{ name: 'Ivan Magalhaes', image: 'ivanmagalhaes.png' },
|
|
{ name: 'Onyama Limba', image: 'onyamalimba.png' },
|
|
{ name: 'Stephen Shaw', image: 'stephenshaw.png' },
|
|
{ name: 'XuXue Feng', image: 'xuxuefeng.png' },
|
|
];
|
|
|
|
const statuses = [
|
|
'unqualified',
|
|
'qualified',
|
|
'new',
|
|
'negotiation',
|
|
'renewal',
|
|
'proposal',
|
|
];
|
|
|
|
const customerService = new CustomerService();
|
|
const productService = new ProductService();
|
|
|
|
useEffect(() => {
|
|
setLoading2(true);
|
|
|
|
customerService.getCustomersLarge().then((data) => {
|
|
setCustomers1(getCustomers(data));
|
|
setLoading1(false);
|
|
});
|
|
customerService.getCustomersLarge().then((data) => {
|
|
setCustomers2(getCustomers(data));
|
|
setLoading2(false);
|
|
});
|
|
customerService.getCustomersMedium().then((data) => setCustomers3(data));
|
|
productService
|
|
.getProductsWithOrdersSmall()
|
|
.then((data) => setProducts(data));
|
|
|
|
initFilters1();
|
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
|
|
const balanceTemplate = (rowData) => {
|
|
return <span className="text-bold">{formatCurrency(rowData.balance)}</span>;
|
|
};
|
|
|
|
const getCustomers = (data) => {
|
|
return [...(data || [])].map((d) => {
|
|
d.date = new Date(d.date);
|
|
return d;
|
|
});
|
|
};
|
|
|
|
const formatDate = (value) => {
|
|
return value.toLocaleDateString('en-US', {
|
|
day: '2-digit',
|
|
month: '2-digit',
|
|
year: 'numeric',
|
|
});
|
|
};
|
|
|
|
const formatCurrency = (value) => {
|
|
return value.toLocaleString('en-US', {
|
|
style: 'currency',
|
|
currency: 'USD',
|
|
});
|
|
};
|
|
|
|
const initFilters1 = () => {
|
|
setFilters1({
|
|
global: { value: null, matchMode: FilterMatchMode.CONTAINS },
|
|
name: {
|
|
operator: FilterOperator.AND,
|
|
constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }],
|
|
},
|
|
'country.name': {
|
|
operator: FilterOperator.AND,
|
|
constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }],
|
|
},
|
|
representative: { value: null, matchMode: FilterMatchMode.IN },
|
|
date: {
|
|
operator: FilterOperator.AND,
|
|
constraints: [{ value: null, matchMode: FilterMatchMode.DATE_IS }],
|
|
},
|
|
balance: {
|
|
operator: FilterOperator.AND,
|
|
constraints: [{ value: null, matchMode: FilterMatchMode.EQUALS }],
|
|
},
|
|
status: {
|
|
operator: FilterOperator.OR,
|
|
constraints: [{ value: null, matchMode: FilterMatchMode.EQUALS }],
|
|
},
|
|
activity: { value: null, matchMode: FilterMatchMode.BETWEEN },
|
|
verified: { value: null, matchMode: FilterMatchMode.EQUALS },
|
|
});
|
|
};
|
|
|
|
const countryBodyTemplate = (rowData) => {
|
|
return (
|
|
<React.Fragment>
|
|
<img
|
|
alt="flag"
|
|
src="assets/demo/images/flags/flag_placeholder.png"
|
|
className={`flag flag-${rowData.country.code}`}
|
|
width={30}
|
|
/>
|
|
<span
|
|
style={{ marginLeft: '.5em', verticalAlign: 'middle' }}
|
|
className="image-text"
|
|
>
|
|
{rowData.country.name}
|
|
</span>
|
|
</React.Fragment>
|
|
);
|
|
};
|
|
|
|
const filterClearTemplate = (options) => {
|
|
return (
|
|
<Button
|
|
type="button"
|
|
icon="pi pi-times"
|
|
onClick={options.filterClearCallback}
|
|
className="p-button-secondary"
|
|
></Button>
|
|
);
|
|
};
|
|
|
|
const filterApplyTemplate = (options) => {
|
|
return (
|
|
<Button
|
|
type="button"
|
|
icon="pi pi-check"
|
|
onClick={options.filterApplyCallback}
|
|
className="p-button-success"
|
|
></Button>
|
|
);
|
|
};
|
|
|
|
const representativeBodyTemplate = (rowData) => {
|
|
const representative = rowData.representative;
|
|
return (
|
|
<React.Fragment>
|
|
<img
|
|
alt={representative.name}
|
|
src={`images/avatar/${representative.image}`}
|
|
onError={(e) =>
|
|
(e.target.src =
|
|
'https://www.primefaces.org/wp-content/uploads/2020/05/placeholder.png')
|
|
}
|
|
width={32}
|
|
style={{ verticalAlign: 'middle' }}
|
|
/>
|
|
<span
|
|
style={{ marginLeft: '.5em', verticalAlign: 'middle' }}
|
|
className="image-text"
|
|
>
|
|
{representative.name}
|
|
</span>
|
|
</React.Fragment>
|
|
);
|
|
};
|
|
|
|
const representativeFilterTemplate = (options) => {
|
|
return (
|
|
<>
|
|
<div className="mb-3 text-bold">Agent Picker</div>
|
|
<MultiSelect
|
|
value={options.value}
|
|
options={representatives}
|
|
itemTemplate={representativesItemTemplate}
|
|
onChange={(e) => options.filterCallback(e.value)}
|
|
optionLabel="name"
|
|
placeholder="Any"
|
|
className="p-column-filter"
|
|
/>
|
|
</>
|
|
);
|
|
};
|
|
|
|
const representativesItemTemplate = (option) => {
|
|
return (
|
|
<div className="p-multiselect-representative-option">
|
|
<img
|
|
alt={option.name}
|
|
src={`assets/demo/images/avatar/${option.image}`}
|
|
width={32}
|
|
style={{ verticalAlign: 'middle' }}
|
|
/>
|
|
<span
|
|
style={{ marginLeft: '.5em', verticalAlign: 'middle' }}
|
|
className="image-text"
|
|
>
|
|
{option.name}
|
|
</span>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const dateBodyTemplate = (rowData) => {
|
|
return formatDate(rowData.date);
|
|
};
|
|
|
|
const dateFilterTemplate = (options) => {
|
|
return (
|
|
<Calendar
|
|
value={options.value}
|
|
onChange={(e) => options.filterCallback(e.value, options.index)}
|
|
dateFormat="mm/dd/yy"
|
|
placeholder="mm/dd/yyyy"
|
|
mask="99/99/9999"
|
|
/>
|
|
);
|
|
};
|
|
|
|
const balanceBodyTemplate = (rowData) => {
|
|
return formatCurrency(rowData.balance);
|
|
};
|
|
|
|
const balanceFilterTemplate = (options) => {
|
|
return (
|
|
<InputNumber
|
|
value={options.value}
|
|
onChange={(e) => options.filterCallback(e.value, options.index)}
|
|
mode="currency"
|
|
currency="USD"
|
|
locale="en-US"
|
|
/>
|
|
);
|
|
};
|
|
|
|
const statusBodyTemplate = (rowData) => {
|
|
return (
|
|
<span className={`customer-badge status-${rowData.status}`}>
|
|
{rowData.status}
|
|
</span>
|
|
);
|
|
};
|
|
|
|
const statusFilterTemplate = (options) => {
|
|
return (
|
|
<Dropdown
|
|
value={options.value}
|
|
options={statuses}
|
|
onChange={(e) => options.filterCallback(e.value, options.index)}
|
|
itemTemplate={statusItemTemplate}
|
|
placeholder="Select a Status"
|
|
className="p-column-filter"
|
|
showClear
|
|
/>
|
|
);
|
|
};
|
|
|
|
const statusItemTemplate = (option) => {
|
|
return <span className={`customer-badge status-${option}`}>{option}</span>;
|
|
};
|
|
|
|
const activityBodyTemplate = (rowData) => {
|
|
return (
|
|
<ProgressBar
|
|
value={rowData.activity}
|
|
showValue={false}
|
|
style={{ height: '.5rem' }}
|
|
></ProgressBar>
|
|
);
|
|
};
|
|
|
|
const activityFilterTemplate = (options) => {
|
|
return (
|
|
<React.Fragment>
|
|
<Slider
|
|
value={options.value}
|
|
onChange={(e) => options.filterCallback(e.value)}
|
|
range
|
|
className="m-3"
|
|
></Slider>
|
|
<div className="flex align-items-center justify-content-between px-2">
|
|
<span>{options.value ? options.value[0] : 0}</span>
|
|
<span>{options.value ? options.value[1] : 100}</span>
|
|
</div>
|
|
</React.Fragment>
|
|
);
|
|
};
|
|
|
|
const verifiedBodyTemplate = (rowData) => {
|
|
return (
|
|
<i
|
|
className={classNames('pi', {
|
|
'text-green-500 pi-check-circle': rowData.verified,
|
|
'text-pink-500 pi-times-circle': !rowData.verified,
|
|
})}
|
|
></i>
|
|
);
|
|
};
|
|
|
|
const verifiedFilterTemplate = (options) => {
|
|
return (
|
|
<TriStateCheckbox
|
|
value={options.value}
|
|
onChange={(e) => options.filterCallback(e.value)}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const expandAll = () => {
|
|
let _expandedRows = {};
|
|
products.forEach((p) => (_expandedRows[`${p.id}`] = true));
|
|
|
|
setExpandedRows(_expandedRows);
|
|
};
|
|
|
|
const collapseAll = () => {
|
|
setExpandedRows(null);
|
|
};
|
|
|
|
const amountBodyTemplate = (rowData) => {
|
|
return formatCurrency(rowData.amount);
|
|
};
|
|
|
|
const statusOrderBodyTemplate = (rowData) => {
|
|
return (
|
|
<span className={`order-badge order-${rowData.status.toLowerCase()}`}>
|
|
{rowData.status}
|
|
</span>
|
|
);
|
|
};
|
|
|
|
const searchBodyTemplate = () => {
|
|
return <Button icon="pi pi-search" />;
|
|
};
|
|
|
|
const imageBodyTemplate = (rowData) => {
|
|
return (
|
|
<img
|
|
src={`images/product/${rowData.image}`}
|
|
onError={(e) =>
|
|
(e.target.src =
|
|
'https://www.primefaces.org/wp-content/uploads/2020/05/placeholder.png')
|
|
}
|
|
alt={rowData.image}
|
|
className="shadow-2"
|
|
width={100}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const priceBodyTemplate = (rowData) => {
|
|
return formatCurrency(rowData.price);
|
|
};
|
|
|
|
const ratingBodyTemplate = (rowData) => {
|
|
return <Rating value={rowData.rating} readOnly cancel={false} />;
|
|
};
|
|
|
|
const statusBodyTemplate2 = (rowData) => {
|
|
return (
|
|
<span
|
|
className={`product-badge status-${rowData.inventoryStatus.toLowerCase()}`}
|
|
>
|
|
{rowData.inventoryStatus}
|
|
</span>
|
|
);
|
|
};
|
|
|
|
const rowExpansionTemplate = (data) => {
|
|
return (
|
|
<div className="orders-subtable">
|
|
<h5>Orders for {data.name}</h5>
|
|
<DataTable value={data.orders} responsiveLayout="scroll">
|
|
<Column field="id" header="Id" sortable></Column>
|
|
<Column field="customer" header="Customer" sortable></Column>
|
|
<Column field="date" header="Date" sortable></Column>
|
|
<Column
|
|
field="amount"
|
|
header="Amount"
|
|
body={amountBodyTemplate}
|
|
sortable
|
|
></Column>
|
|
<Column
|
|
field="status"
|
|
header="Status"
|
|
body={statusOrderBodyTemplate}
|
|
sortable
|
|
></Column>
|
|
<Column
|
|
headerStyle={{ width: '4rem' }}
|
|
body={searchBodyTemplate}
|
|
></Column>
|
|
</DataTable>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const header = (
|
|
<div className="table-header-container">
|
|
<Button
|
|
icon="pi pi-plus"
|
|
label="Expand All"
|
|
onClick={expandAll}
|
|
className="mr-2 mb-2"
|
|
/>
|
|
<Button
|
|
icon="pi pi-minus"
|
|
label="Collapse All"
|
|
onClick={collapseAll}
|
|
className="mb-2"
|
|
/>
|
|
</div>
|
|
);
|
|
|
|
const headerTemplate = (data) => {
|
|
return (
|
|
<React.Fragment>
|
|
<img
|
|
alt={data.representative.name}
|
|
src={`images/avatar/${data.representative.image}`}
|
|
width="32"
|
|
style={{ verticalAlign: 'middle' }}
|
|
/>
|
|
<span className="image-text font-bold">{data.representative.name}</span>
|
|
</React.Fragment>
|
|
);
|
|
};
|
|
|
|
const footerTemplate = (data) => {
|
|
return (
|
|
<React.Fragment>
|
|
<td
|
|
colSpan="4"
|
|
style={{ textAlign: 'right' }}
|
|
className="text-bold pr-6"
|
|
>
|
|
Total Customers
|
|
</td>
|
|
<td>{calculateCustomerTotal(data.representative.name)}</td>
|
|
</React.Fragment>
|
|
);
|
|
};
|
|
|
|
const calculateCustomerTotal = (name) => {
|
|
let total = 0;
|
|
|
|
if (customers3) {
|
|
for (let customer of customers3) {
|
|
if (customer.representative.name === name) {
|
|
total++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return total;
|
|
};
|
|
|
|
return (
|
|
<div className="grid table-demo">
|
|
<div className="col-12">
|
|
<div className="card">
|
|
<h5>Filter Menu</h5>
|
|
<DataTable
|
|
value={customers1}
|
|
paginator
|
|
className="p-datatable-gridlines"
|
|
showGridlines
|
|
rows={10}
|
|
dataKey="id"
|
|
filters={filters1}
|
|
filterDisplay="menu"
|
|
loading={loading1}
|
|
responsiveLayout="scroll"
|
|
emptyMessage="No customers found."
|
|
>
|
|
<Column
|
|
field="name"
|
|
header="Name"
|
|
filter
|
|
filterPlaceholder="Search by name"
|
|
style={{ minWidth: '12rem' }}
|
|
/>
|
|
<Column
|
|
header="Country"
|
|
filterField="country.name"
|
|
style={{ minWidth: '12rem' }}
|
|
body={countryBodyTemplate}
|
|
filter
|
|
filterPlaceholder="Search by country"
|
|
filterClear={filterClearTemplate}
|
|
filterApply={filterApplyTemplate}
|
|
/>
|
|
<Column
|
|
header="Agent"
|
|
filterField="representative"
|
|
showFilterMatchModes={false}
|
|
filterMenuStyle={{ width: '14rem' }}
|
|
style={{ minWidth: '14rem' }}
|
|
body={representativeBodyTemplate}
|
|
filter
|
|
filterElement={representativeFilterTemplate}
|
|
/>
|
|
<Column
|
|
header="Date"
|
|
filterField="date"
|
|
dataType="date"
|
|
style={{ minWidth: '10rem' }}
|
|
body={dateBodyTemplate}
|
|
filter
|
|
filterElement={dateFilterTemplate}
|
|
/>
|
|
<Column
|
|
header="Balance"
|
|
filterField="balance"
|
|
dataType="numeric"
|
|
style={{ minWidth: '10rem' }}
|
|
body={balanceBodyTemplate}
|
|
filter
|
|
filterElement={balanceFilterTemplate}
|
|
/>
|
|
<Column
|
|
field="status"
|
|
header="Status"
|
|
filterMenuStyle={{ width: '14rem' }}
|
|
style={{ minWidth: '12rem' }}
|
|
body={statusBodyTemplate}
|
|
filter
|
|
filterElement={statusFilterTemplate}
|
|
/>
|
|
<Column
|
|
field="activity"
|
|
header="Activity"
|
|
showFilterMatchModes={false}
|
|
style={{ minWidth: '12rem' }}
|
|
body={activityBodyTemplate}
|
|
filter
|
|
filterElement={activityFilterTemplate}
|
|
/>
|
|
<Column
|
|
field="verified"
|
|
header="Verified"
|
|
dataType="boolean"
|
|
bodyClassName="text-center"
|
|
style={{ minWidth: '8rem' }}
|
|
body={verifiedBodyTemplate}
|
|
filter
|
|
filterElement={verifiedFilterTemplate}
|
|
/>
|
|
</DataTable>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="col-12">
|
|
<div className="card">
|
|
<h5>Frozen Columns</h5>
|
|
<ToggleButton
|
|
checked={idFrozen}
|
|
onChange={(e) => setIdFrozen(e.value)}
|
|
onIcon="pi pi-lock"
|
|
offIcon="pi pi-lock-open"
|
|
onLabel="Unfreeze Id"
|
|
offLabel="Freeze Id"
|
|
style={{ width: '10rem' }}
|
|
/>
|
|
|
|
<DataTable
|
|
value={customers2}
|
|
scrollable
|
|
scrollHeight="400px"
|
|
loading={loading2}
|
|
scrollDirection="both"
|
|
className="mt-3"
|
|
>
|
|
<Column
|
|
field="name"
|
|
header="Name"
|
|
style={{ flexGrow: 1, flexBasis: '160px' }}
|
|
frozen
|
|
></Column>
|
|
<Column
|
|
field="id"
|
|
header="Id"
|
|
style={{ flexGrow: 1, flexBasis: '100px' }}
|
|
frozen={idFrozen}
|
|
alignFrozen="left"
|
|
></Column>
|
|
<Column
|
|
field="name"
|
|
header="Name"
|
|
style={{ flexGrow: 1, flexBasis: '200px' }}
|
|
></Column>
|
|
<Column
|
|
field="country.name"
|
|
header="Country"
|
|
style={{ flexGrow: 1, flexBasis: '200px' }}
|
|
body={countryBodyTemplate}
|
|
></Column>
|
|
<Column
|
|
field="date"
|
|
header="Date"
|
|
style={{ flexGrow: 1, flexBasis: '200px' }}
|
|
body={dateBodyTemplate}
|
|
></Column>
|
|
<Column
|
|
field="company"
|
|
header="Company"
|
|
style={{ flexGrow: 1, flexBasis: '200px' }}
|
|
></Column>
|
|
<Column
|
|
field="status"
|
|
header="Status"
|
|
style={{ flexGrow: 1, flexBasis: '200px' }}
|
|
body={statusBodyTemplate}
|
|
></Column>
|
|
<Column
|
|
field="activity"
|
|
header="Activity"
|
|
style={{ flexGrow: 1, flexBasis: '200px' }}
|
|
></Column>
|
|
<Column
|
|
field="representative.name"
|
|
header="Representative"
|
|
style={{ flexGrow: 1, flexBasis: '200px' }}
|
|
body={representativeBodyTemplate}
|
|
></Column>
|
|
<Column
|
|
field="balance"
|
|
header="Balance"
|
|
body={balanceTemplate}
|
|
frozen
|
|
style={{ flexGrow: 1, flexBasis: '120px' }}
|
|
alignFrozen="right"
|
|
></Column>
|
|
</DataTable>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="col-12">
|
|
<div className="card">
|
|
<h5>Row Expand</h5>
|
|
<DataTable
|
|
value={products}
|
|
expandedRows={expandedRows}
|
|
onRowToggle={(e) => setExpandedRows(e.data)}
|
|
responsiveLayout="scroll"
|
|
rowExpansionTemplate={rowExpansionTemplate}
|
|
dataKey="id"
|
|
header={header}
|
|
>
|
|
<Column expander style={{ width: '3em' }} />
|
|
<Column field="name" header="Name" sortable />
|
|
<Column header="Image" body={imageBodyTemplate} />
|
|
<Column
|
|
field="price"
|
|
header="Price"
|
|
sortable
|
|
body={priceBodyTemplate}
|
|
/>
|
|
<Column field="category" header="Category" sortable />
|
|
<Column
|
|
field="rating"
|
|
header="Reviews"
|
|
sortable
|
|
body={ratingBodyTemplate}
|
|
/>
|
|
<Column
|
|
field="inventoryStatus"
|
|
header="Status"
|
|
sortable
|
|
body={statusBodyTemplate2}
|
|
/>
|
|
</DataTable>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="col-12">
|
|
<div className="card">
|
|
<h5>Subheader Grouping</h5>
|
|
<DataTable
|
|
value={customers3}
|
|
rowGroupMode="subheader"
|
|
groupRowsBy="representative.name"
|
|
sortMode="single"
|
|
sortField="representative.name"
|
|
sortOrder={1}
|
|
scrollable
|
|
scrollHeight="400px"
|
|
rowGroupHeaderTemplate={headerTemplate}
|
|
rowGroupFooterTemplate={footerTemplate}
|
|
responsiveLayout="scroll"
|
|
>
|
|
<Column
|
|
field="name"
|
|
header="Name"
|
|
style={{ minWidth: '200px' }}
|
|
></Column>
|
|
<Column
|
|
field="country"
|
|
header="Country"
|
|
body={countryBodyTemplate}
|
|
style={{ minWidth: '200px' }}
|
|
></Column>
|
|
<Column
|
|
field="company"
|
|
header="Company"
|
|
style={{ minWidth: '200px' }}
|
|
></Column>
|
|
<Column
|
|
field="status"
|
|
header="Status"
|
|
body={statusBodyTemplate}
|
|
style={{ minWidth: '200px' }}
|
|
></Column>
|
|
<Column
|
|
field="date"
|
|
header="Date"
|
|
style={{ minWidth: '200px' }}
|
|
></Column>
|
|
</DataTable>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const comparisonFn = function (prevProps, nextProps) {
|
|
return prevProps.location.pathname === nextProps.location.pathname;
|
|
};
|
|
|
|
export default React.memo(TableDemo, comparisonFn);
|