376 lines
19 KiB
JavaScript
376 lines
19 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);
|